function rowData (gr, cr, ch, rg)
{
	this.grade = gr;
	this.credits = parseInt (cr);
	this.checked = ch;
	this.repgrade = rg;
}

function markBox (x)  //Indicate an error
{
	return true;
}

function chkbox (n)
{
	document.forms["GPAForm"]["REPEAT"+n].checked = true;
}

function manage (r)
{
	if (document.forms["GPAForm"]["REPEAT"+r].checked == false) 
		document.forms["GPAForm"]["REPGRADE"+r].value = "";	 
	else
		document.forms["GPAForm"]["REPGRADE"+r].focus ();
	return true;
}	

function clearRow (i) //Erase row data by number
{
	document.forms["GPAForm"]["COURSE"+i].value = "";
	document.forms["GPAForm"]["GR"+i].value = "";
	document.forms["GPAForm"]["CR"+i].value = "";
	document.forms["GPAForm"]["REPEAT"+i].checked = false; 	
	manage (i);
}

function eraseAll ()
{
	var i = 0;

	for (i; i < 8; i++) clearRow (i);
	document.forms["GPAForm"]["CUMUL_GPA"].value = ""; 	
	document.forms["GPAForm"]["CUMUL_CREDITS"].value = ""; 	
	return true;
}

function fixGrade (g)  // g is a string literal
{
	var r;                       //Counter for checking grade patterns
	var s;                       //Nested counter for checking for fixable grade patterns
	var gre = new Array (0);     //Acceptable grade patterns 
	gre[gre.length] = /^A[-]?$/;
	gre[gre.length] = /^[BCD][+-]?$/;
	gre[gre.length] = /^[EWSUI]$/;

	var gfre = new Array (0);    //Grade fixable patterns 
	gfre[gfre.length]= /^a[-]?$/; 
	gfre[gfre.length] = /^[bcd][+-]?$/;
	gfre[gfre.length] = /^[eusiw]$/;

	var fixable = new Boolean (true); //true: we fixed it, false: not fixable

	for (r = 0; r < gre.length && !gre[r].test (g); r++); //Check grade out
	if (r >= gre.length)  //Can't find "preferred" input, try to fix
	{
		for (s = 0; s < gfre.length && !gfre[s].test (g); s++);
		if (s >= gfre.length) fixable = false;  //Can't repair g 
	}
	return fixable; //If it isn't broken don't fix it. Can't fix it? Don't try.
}

function fixRow (i) //Check for row errors, write errors to array errors
{
	var errors = new Array (0);  //Contains all error strings
	var cre0 = /^[0-9][0-9]?$/;  //Valid credit regular expressions
	var grade = document.forms["GPAForm"]["GR"+i].value; //string literal
	var credits = document.forms["GPAForm"]["CR"+i].value; //string literal
	var checked = document.forms["GPAForm"]["REPEAT"+i].checked;
	var repgrade = document.forms["GPAForm"]["REPGRADE"+i].value;  //Repeated grade if needed

	if (grade) //Grade given 
	{
		if (!credits) //Check for credits
		{
			errors[errors.length] = "Course " + (i+1) + ":  Credits are missing.";
			markBox (document.forms["GPAForm"]["CR"+i]);
		}
		else
		{
			if (!cre0.test (credits))  //Check for bad credit input
				errors[errors.length] = "Course " + (i+1) + ": Credits given were not valid.";
			if (fixGrade (grade) == false) //Bad input we can't fix
			{
				errors [errors.length] = "Course " + (i+1) + ": An invalid grade was given.";
				markBox (document.forms["GPAForm"]["GR"+i]);
			}	
			else
				document.forms["GPAForm"]["GR"+i].value = grade.toUpperCase (); //Fix the grade.  This is only turning the string into capital letters.
		}
		if (checked == true)
		{
			if (!repgrade)
			{
				errors[errors.length] = "Course " + (i+1) + ":  You have indicated there is a repeated grade but none was given.";
				markBox (document.forms["GPAForm"]["REPEAT"+i]);
			}
			else
			{
				if (fixGrade (repgrade) == false)
				{
					errors[errors.length] = "Course " + (i+1) + ":  The repeat grade was not valid.";
					markBox (document.forms["GPAForm"]["REPEAT"+i]);
				}
				else
					document.forms["GPAForm"]["REPGRADE"+i].value = repgrade.toUpperCase ();
			}
		}
		return errors;
	}

//NO GRADE GIVEN.  FIND OUT IF WE HAVE A BLANK ROW.

	if (credits)  //Gave credits but forgot to give a grade.
	{
		markBox (document.forms["GPAForm"]["CR"+i]); 
		errors[errors.length] = "Course " + (i+1) + ":  No grade given.";
		if (!cre0.test (credits))
		{
			errors[errors.length] = "Course " + (i+1) + ":  Credits given were not valid.";
			markBox (document.forms["GPAForm"]["CR"+i]);
		}
		if (document.forms["GPAForm"]["CR"+i].checked == true)
		{
			repgrade = document.forms["GPAForm"]["REPGRADE"+i].value;
			if (!repgrade)
			{
				errors[errors.length] = "Course " + (i+1) + ":  You have indicated that you have repeated this course but no previous grade was given.";
				markBox (document.forms["GPAForm"]["REPGRADE"+i]);
			}
			else
			{
				if (fixGrade (repgrade) == false)
				{
					errors[errors.length] = "Course " + (i+1) + ":  An invalid repeated grade was given.";
					markBox (document.forms["GPAForm"]["REPGRADE"+i]);
				}
				else document.forms["GPAForm"]["CR"+i].value = grade.toUpperCase ();
			}
		}
		return errors;
	}

//NO GRADE OR CREDITS GIVEN.  CONTINUE TO CHECK TO SEE IF THE ROW IS BLANK.

	if (checked == true)
	{
		if (!repgrade)
		{
			errors[errors.length] = "Repeated course " + (i+1) + ": You indicated a course was repeated but no grades or credits were given.";
			markBox (document.forms["GPAForm"]["REPGRADE"+i]);
			markBox (document.forms["GPAForm"]["CR"+i]); 
			markBox (document.forms["GPAForm"]["GR"+i]);  
		}
	}
	return errors;  //send the errors somewhere else to be displayed
}
	 
function scanForm ()  //Return a list of lists of errors for display
{
	var j; //Loop counter

	var errstack = new Array (0); //A list of errors from each row.  Not a stack.
	var gpaError = new Array (0);
	var credError = new Array (0);
 
	var cgpare0 = /^4\.0?0?$/;  //Valid cumulative GPA regular expressions
	var cgpare1 = /^[0-4](\.)?$/;
	var cgpare2 = /^[0-3](\.[0-9][0-9]?)$/;
	var ccredre0 = /^[0-9][0-9]*$/;

	var gpa = document.forms["GPAForm"]["CUMUL_GPA"].value;
	var creds = document.forms["GPAForm"]["CUMUL_CREDITS"].value;

	for (j = 0; j < 8; j++)
		if (tempstr = fixRow (j), tempstr.length > 0)
			errstack[errstack.length] = tempstr; //Get all errors from each row
	if (gpa)  // A cumulative GPA has been entered for the script
	{
		if (!(cgpare0.test (gpa) || cgpare1.test (gpa) || cgpare2.test (gpa)))
		{
			markBox (document.forms["GPAForm"]["CUMUL_GPA"]);
			credError[credError.length] = "Cumulative GPA: An invalid score was given.";
		}
		if (!creds)
		{
			credError[credError.length] = "Cumulative Credits: None were entered with your cumulative GPA.";
		}
		else 
		{
			if (!ccredre0.test (creds))
			{
				markBox (document.forms["GPAForm"]["CUMUL_CREDITS"]);
				credError[credError.length] = "Cumulative Credits: An invalid number was given.";
			}
			else
			{
				if (parseInt (gpa) > 0 && parseInt (creds) == 0)
					errstack[errstack.length] = new Array ("You have indicated your cumulative GPA is " + gpa.toString (10) + " without earning any credits.");
			}
		}
		if (gpaError.length > 0) errstack[errstack.length] = gpaError;
		if (credError.length > 0) errstack[errstack.length] = credError;
		return errstack;
	}
	
	//No GPA given here.

	if (creds && !(/^0+$/.test (creds)))
	{
		markBox (document.forms["GPAForm"]["CUMUL_GPA"]);
		gpaError[gpaError.length] = "Cumulative GPA: No GPA was given with your cumulative credits.";
	}
	if (gpaError.length > 0) errstack[errstack.length] = gpaError;
	if (credError.length > 0) errstack[errstack.length] = credError;
	return errstack;
}

function matchString (s, setOfStrings)
//
// RETURNS THE INDEX WHERE s IS FOUND IN THE ARRAY setOfStrings.
//
{
	for (var i = 0; i < setOfStrings.length; i++)
	{
		if (s == setOfStrings[i])
			return i;
	}
	return i;
}	

function computeGPA (allRows, cumulRow)
{
	var letterGrades = new Array ("A", "A-", "B+", "B", "B-", "C+", "C", "C-", "D+", "D", "D-", "E");
	var matchingQPoints = new Array (4.00, 3.67, 3.33, 3.00, 2.67, 2.33, 2.00, 1.67, 1.33, 1.00, 0.67, 0.00);
	var iwGrades = new Array ("W", "I");
	var pfGrades = new Array ("S", "U");
	var failGrade = new Array ("E");

	var i;             // Loop counter
	var earnedCredits = cumulRow[1];
	var qualityPts = earnedCredits * cumulRow[0]; 
	var attemptedCredits = earnedCredits; //Only have earned credits here

	for (i = 0; i < allRows.length; i++)
	{
		if (matchString (allRows[i].grade, letterGrades) < letterGrades.length)
		{
			//
			// STUDENT RECEIVED A LETTER GRADE AND NOT A PASS/FAIL OR I OR W GRADE.
			//

			if (allRows[i].checked == true)
			{
				//
				// THIS IS A REPEATED GRADE
				//

				qualityPts -=  allRows[i].credits * matchingQPoints[matchString (allRows[i].repgrade, letterGrades)];
				attemptedCredits -= allRows[i].credits;
			}
			qualityPts +=  allRows[i].credits * matchingQPoints[matchString (allRows[i].grade, letterGrades)];
			attemptedCredits += allRows[i].credits;
		}
	}
	return attemptedCredits == 0 ? 0 : qualityPts / attemptedCredits;
}

function showHelp (page)
{
	var outWindow = window.open(page, "helpWindow", "width=324,height=380,scrollbars=yes"); 
	outWindow.focus();
}

function showProblems (eStack)
{
	var i;
	var j;
	var fire = window.open ("", "ProblemWindows", "scrollbars=yes,status=no,height=300,width=250,menubar=no,resizable=yes,toolbar=no");
	fire.focus ();
	fire.document.open ();
	fire.document.write ("<HTML><HEAD><TITLE>There Were Mistakes</TITLE><LINK href=\"styles/gpacalc.css\" type=\"text/css\" rel=\"stylesheet\"></HEAD><BODY><SPAN class=\"section\">Hold on! We found some mistakes.  Here's what's wrong:</SPAN><BR><BR>");
	for (i = 0; eStack[i]; i++)
		for (j = 0; eStack[i][j]; j++)
			fire.document.write (eStack[i][j] + "<BR><BR>");
	fire.document.write ("</BODY></HTML>");	
	fire.document.close ();
}

function scanForErrors ()
{
	{
		for (var i = 0; i < 8 && !document.forms["GPAForm"]["GR"+i].value; i++);
		if (!document.forms["GPAForm"]["CUMUL_GPA"].value) i++;
		if (i == 9) return "We can't calculate your GPA because no grades were given."
	}

// AT LEAST ONE GRADE GIVEN OR A CUMULATIVE GPA GIVEN

	{
		for (var i = 0; i < 8 && document.forms["GPAForm"]["REPEAT"+i].checked == false; i++);
		if (i < 8 && !document.forms["GPAForm"]["CUMUL_CREDITS"].value)
			return "We will need your cumulative credits and your cumulative GPA if you repeated a course."; 
	}

// CUMULATIVE CREDITS GIVEN IF A REPEATED COURSE INDICATED

	{
		var failingRE = /^[WUSEI]$/;
		var passingCredTotal = 0;
		var i;	
		for (i = 0; i < 8; i++)
			if (document.forms["GPAForm"]["REPEAT"+i].checked == true && !failingRE.test (document.forms["GPAForm"]["REPGRADE"+i].value))
				passingCredTotal += parseInt (document.forms["GPAForm"]["CR"+i].value);
		if (passingCredTotal > parseInt (document.forms["GPAForm"]["CUMUL_CREDITS"].value)) return "You have indicated that you are repeating more credits than you have earned.";
	}
	{
	}
	return "";
}

function buildGPAArray (allRows, cumulRow)
{
	for (var i = 0; i < 8; i++) if (document.forms["GPAForm"]["GR"+i].value)
		if (document.forms["GPAForm"]["GR"+i].value)
		{
			allRows[allRows.length] = new rowData (document.forms["GPAForm"]["GR"+i].value, document.forms["GPAForm"]["CR"+i].value, document.forms["GPAForm"]["REPEAT"+i].checked, document.forms["GPAForm"]["REPEAT"+i].checked == false ? "" : document.forms["GPAForm"]["REPGRADE"+i].value);
		}
	if (document.forms["GPAForm"]["CUMUL_GPA"].value)
	{
		cumulRow[cumulRow.length] = parseFloat (document.forms["GPAForm"]["CUMUL_GPA"].value);
		cumulRow[cumulRow.length] = parseInt (document.forms["GPAForm"]["CUMUL_CREDITS"].value);
		return;
	}
	cumulRow[cumulRow.length] = 0;
	cumulRow[cumulRow.length] = 0;
}

function buildReportCard (gpa)
{
	var s = '<HTML><HEAD><LINK href="styles/gpacalc.css" type="text/css" rel="stylesheet"><TITLE>Your Grade Summary</TITLE></HEAD><BODY><TABLE align="center" cellpadding=5 cellspacing=5 width="70%"><TR><TH>Course Name</TH><TH>Grade</TH><TH>Credits</TH><TH>Repeated</TH><TH>Old Grade</TH></TR>';

	for (var i = 0; i < 8; i++)
	{
		
		if (document.forms["GPAForm"]["GR"+i].value)
		{
			s += "<TR><TD><CENTER>";
			if (document.forms["GPAForm"]["COURSE"+i].value)
				s += document.forms["GPAForm"]["COURSE"+i].value;
			else
				s += "COURSE " + (i+1);
			s += "</CENTER></TD><TD><CENTER>" + document.forms["GPAForm"]["GR"+i].value + "</CENTER></TD><TD><CENTER>" + document.forms["GPAForm"]["CR"+i].value + "</CENTER></TD>";
			
			if (document.forms["GPAForm"]["REPEAT"+i].checked == true)
				s += "<TD><CENTER>" + "R" + "</CENTER></TD><TD><CENTER>" + document.forms["GPAForm"]["REPGRADE"+i].value + "</CENTER></TD>";
			else
				s += "<TD></TD>";
			s += "</TR>";
		}
	}	

	var intGradeRe = /^[0-4]$/;        //Possible grade patterns
	var decGradeRe = /^[0-3]\.[0-9]$/;
	var t = gpa.toString ().substring (0, 5);
	if (intGradeRe.test (t))
		t += (".00");
	else if (decGradeRe.test (t))
		t += "0";

	s += "</TABLE><BR><BR><TABLE align=\"center\" cellpadding=5 cellspacing=5 width = \"70%\"><TR><TH><CENTER>Previous Cumulative GPA</CENTER></TH><TH><CENTER>Previous Cumulative Credits</CENTER></TH><TH><CENTER>YOUR GPA</CENTER></TH></TR>";
	if (document.forms["GPAForm"]["CUMUL_GPA"].value)
		s += "<TR><TD><CENTER>" + document.forms["GPAForm"]["CUMUL_GPA"].value + "</CENTER></TD><TD><CENTER>" + document.forms["GPAForm"]["CUMUL_CREDITS"].value + "</CENTER></TD><TD class=\"highlight\"><CENTER><B>" + t + "</B></CENTER></TD></TR>";
	else
		s += "<TR><TD><CENTER>N/A</CENTER></TD><TD><CENTER>N/A</CENTER></TD><TD class=\"highlight\"><CENTER><B>" + t + "</B></CENTER></TD></TR>";
	return s += "</TABLE><BR><BR><BR>Calculation results are based on information you provide in the form's fields, and as such, are not official. For example, if you enter each grade as an \"A,\" the result will be displayed as a 4.0; but this does not necessarily mean that you will earn at semester's end a 4.0.<BR><BR>Written by <A href=\"mailto:cwal0802@brockport.edu\">Cory Walker</A> for SUNY Brockport.</BODY></HTML>";
}

function outputReportCard (s)
{
	var earth = window.open ("", "REPORTWINDOW", "height=500,width=700,resizable=yes,toolbars=yes,scrollbars=yes");
	earth.document.open ();
	earth.document.write (s);
	earth.document.close ();
	earth.focus ();
}

function main ()
{
	errorstack = scanForm (); //Find individual errors in each row
	if (errorstack.length > 0)
	{
		showProblems (errorstack);
		return false;
	}
	delete errorstack;

// NO ROW ERRORS.  NOW CHECK FOR OVERALL FORM ERRORS.

	var s = scanForErrors ();
	if (s != "") //An error message was returned.
	{
		var eStack = new Array (0);
		var entry = new Array (0);
		entry[entry.length] = s;
		eStack[eStack.length] = entry; 
		showProblems (eStack);
		return false;
	}

	var allRows = new Array (0);  //Build for computeGPA function
	var cumulRow = new Array (0); //Cumulative results

	buildGPAArray (allRows, cumulRow);
	var gpa = computeGPA (allRows, cumulRow); 
	outputReportCard (buildReportCard (gpa));
}


