// quizzes.js
// Danny Sauer, November 30, 2000
// dsauer@teleologic.net

// verification functions assume the right answer will have a value of '0'
//  (which is  Boolean false, but they need the number 0), and that incorrect
//  answers will have a greater-than-0 value (negative values are reserved
//  for future use).  Using multiple incorrect values will allow for
//  differing feedback per wrong answer (someday)

// revisions:
// modified checkRadio to accept array-based or single-var (incorrect only)
//  feedback.  Thought about modifying checkCheck similarly, but decided not
//  to since there could exist more than one incorrect checkbox, which makes
//  for an ugly choice between multiple alerts or one *big* dynamically
//  generated alert.  I don't wanna do either if possible, so I won't.
// 1/19/2001	writeCheck added
//          	removed extraneous debugging code from write*
// 9/17/2002    several write* functions added
//              check* functions take optional second arg now
//              checkText and mkDrop functions extended/added
//              various functionality increases with backwards compatability
//              hooray for the saunders site requiring so many new features?
//
/*******************************************************************************
 * begin actual code...
 ******************************************************************************/

// verifies radiobuttons
function checkRadio(daForm, doAlert){
	if(arguments.length == 1){
		var doAlert = true;
	}
	var screwedUp = false;
	var foundRadio = false;
	var radioChecked = false;
	var whichChecked = 1;
	var daImage = new(Image);
	// set ALL of the radiobutton images
	for(var i=0; i<daForm.length; i++){
		daElem = daForm.elements[i];
		if(daElem.type == "radio"){
			foundRadio = true;
			daImage = getImage(daElem.name + "-" + daElem.value);
			if(daElem.checked){
				radioChecked = true;
				whichChecked = parseInt(daElem.value);
				if(parseInt(daElem.value)){
					daImage.src = wrongImage;
					screwedUp = true;
				}else{
					daImage.src = rightImage;
				}
			}else{
				daImage.src = clearImage;
			}
		}
	}
	if(! radioChecked){ screwedUp = true; }
	if(doAlert){
		var thisFormFeedback = eval(daForm.name + "Feedback");
		// yes, this is horrible - but if you want correct feedback
		//  and individual feedback to be an option, it's gotta be done.
		// Feedback can be a string and apply to all questions, or can
		//  be an array with elements corresponding to the form elements
		//  values.  Maybe I should make this even more complex and slow
		//  by rechecking everything over and over?  Ahhh!!!!!
		if(foundRadio){
			if(screwedUp && typeof(thisFormFeedback) == "string"){
				alert(thisFormFeedback);
			}else{
				if(screwedUp ||                             // screwed up test
				   (thisFormFeedback[whichChecked] != "" && // null string test
				   thisFormFeedback[whichChecked])){        // undefined test
					alert(thisFormFeedback[whichChecked]);
				}
			}
		}
	}
	return ! screwedUp;
}

// verifies checkboxen
// only marks unchecked boxes if they checked all the right ones
function checkCheck(daForm, doAlert){
	if(arguments.length == 1){
		var doAlert = true;
	}
	var screwedUp = false;
	var foundChecks = false;
	var j = 0;
	var unCheckedSources = new(Array);
	var unCheckedImages = new(Array);
	var daImage = new(Image);
	for(var i=0; i<daForm.length; i++){
		daElem = daForm.elements[i];
		if(daElem.type == "checkbox"){
			foundChecks = true;
			daImage = getImage(daElem.name);
			if(daElem.checked){
				if(parseInt(daElem.value)){
					daImage.src = wrongImage;
					screwedUp = true;
				}else{
					daImage.src = rightImage;
				}
			}else{
				unCheckedImages[j] = daImage; // deal with this one later
				if(parseInt(daElem.value)){
					daImage.src = clearImage;
					unCheckedSources[j] = rightImage
				}else{
					daImage.src = clearImage;
					unCheckedSources[j] = wrongImage;
					screwedUp = true;
				}
				j++;
			}
		}
	}
	if(doAlert){
		if(foundChecks && screwedUp){
			var thisFormFeedback = eval(daForm.name + "Feedback");
			alert(thisFormFeedback);
		}else{
			// go through and mark the unchecked boxes only if they didn't screw up
			for(i = 0; i < unCheckedImages.length; i++){
				unCheckedImages[i].src = unCheckedSources[i];
			}
		}
	}
	return !screwedUp;
}

// returns an image object with the given name
function getImage(daName){
	return eval("document.images['" + daName + "']");
}

// replaces the first space after the given char# with a newline
//  real useful for things like big alert boxes...  If newlines are
//  already in the string, it pretends that it put them in (so new
//  paragraphs are treated right if you use \n or \n\n to divide them)
function wrapAfter(daPos, daString){
	var i=1;  var spaceAt = 0;  var newlineAt = 0;
	var returnString = "";
	while(daString.length > daPos && i > 0){
		spaceAt=daString.indexOf(" ", daPos);
		spaceAt = spaceAt > 0 ? spaceAt : daString.length;
		newlineAt=daString.lastIndexOf("\n", spaceAt);
		newlineAt = newlineAt > 0 ? newlineAt : daString.length;
		i = Math.min(spaceAt, newlineAt);
		returnString += daString.substring(0,i) + "\n";
		daString = daString.substring(i+1, daString.length);
	}
	return returnString + daString;
}

// generates drop-down list from array of answers with correct index
//  passed in as second arg and the array as the third.  The first arg
//  is the name of the field (name the ok/X image with the same name)
//  eg:  choices a,b,d,c with d being correct - mkDrop('2', 2,['a','b','d','c']);
function mkDrop(cName, cIndex, cAnswers){
	document.write('<SELECT NAME="' + cName + '">');
	for(var i=0;i<cAnswers.length;i++){
		document.write('<OPTION VALUE="');
		document.write(i==cIndex ? 0 : i+1);
		document.write('">' + cAnswers[i] + '</OPTION>');
	}
	document.write('</SELECT>');
}

// makes a single drop-down that checks itself every time
//  based on mkDrop
function mkInstantDrop(cName, cIndex, cAnswers, cFunk){
		if(arguments.length < 4){
			var cFunk = 'checkDrop(this.form)';
		}
		document.write("<FORM NAME=\"" + cName + "\">");
		cName += '" onChange="' + cFunk;
		mkDrop(cName, cIndex, cAnswers);
		document.write("</FORM>\n");
}

// checks drop-down (value of right answer == 0, wrong != 0)
// second argument is whether or not to alert gotWrong feedback
function checkDrop(daForm, doAlert){
	if(arguments.length == 1){
		var doAlert = false;
	}
	var screwedUp = false;
	var foundSelect = false;
	var daImage = new(Image);
	for(var i=0;i<daForm.elements.length;i++){
		daElem = daForm.elements[i];
		if(daElem.type == "select-one"){
			foundSelect = true;
			daImage = getImage(daElem.name);
			if(parseInt(daElem.options[daElem.selectedIndex].value)){
				screwedUp = true;
				daImage.src = wrongImage;
			}else{
				daImage.src = rightImage;
			}
		}
	}
	if(doAlert){
		var thisFormFeedback = eval(daForm.name + "Feedback");
		if(foundSelect && screwedUp){alert(thisFormFeedback)};
	}
	return ! screwedUp;
}

//special checkCheck() for single checkboxes
function checkOneCheck(daElem){
	var returnVal = false;
	if(! daElem.type == "checkbox"){ return false; }
	daImage = getImage(daElem.name);
	// verify value
	if(parseInt(daElem.value)){
		daImage.src = rightImage;
		returnVal = true;
	}else{
		daImage.src = wrongImage;
		returnVal = false;
	}
	return retrunVal;
}

// test text boxes
// the assumption is made that the boxes are named the same as
//  their answer should be, with one letter on the front (to work
//  with numeric answers).  For example, if the answer is 'joe' the
//  text box should be named 'tjoe' or 'xjoe'.  It sucks, but it's
//  also the only way to make numeric answers work.  Name the image
//  the same as the text box.
function checkText(daForm, doAlert){
	if(arguments.length == 1){
		var doAlert = false;
	}
	var screwedUp = false;
	var foundText = false;
	var daImage = new(Image);
	for(var i=0;i<daForm.elements.length;i++){
		daElem = daForm.elements[i];
		if(daElem.type == 'text'){
			foundText = true;
			daImage = getImage(daElem.name);
			if(trimSpace(daElem.value) ==
			   trimSpace(daElem.name.substring(1,daElem.name.length))){
				daImage.src = rightImage;
			}else{
				screwedUp = true;
				daImage.src = wrongImage;
			}
		}
	}
	if(doAlert){
		var thisFormFeedback = eval(daForm.name + "Feedback");
		if(foundText && screwedUp){alert(thisFormFeedback)};
	}
	return ! screwedUp;
}

// writeRadio makes a radiobutton question
// args:
//    radioset = name of radio buttons (same name = radio group)
//    value = >0 is wrong answer, <=0 is right answer
//    questiontext = text of question
//    checkFunc = [optional] function name to check with
//                (defaults to checkRadio)
// expects:
//    clearImage = /path/to/clear/image
//    called in between a <TABLE> and </TABLE> (it generates table rows)
function writeRadio(radioset, value, questionText, checkFunc){
	if(arguments.length == 3){
		var checkFunc = 'checkRadio';
	}
	var imgString = '<IMG SRC="' + clearImage + '" NAME="';
	imgString += radioset + '-' + value + '">';
	
	var radioString = "<INPUT TYPE='RADIO' NAME='" + radioset + "' ";
	radioString += "onClick='"+checkFunc+"(this.form)' VALUE='" + value + "'>";

	document.write('<TR><TD VALIGN="top">' + imgString + '</TD>');
	document.write('<TD VALIGN="top">'+ radioString + '</TD>');
	document.write('<TD WIDTH="100%" class="text_area">' + questionText + '</TD></TR>\n');
}

// writeCheck makes a checkbox question
// args:
//    radioset = name of radio buttons (same name = radio group)
//    value = >0 is wrong answer, <=0 is right answer
//    questiontext = text of question
// expects:
//    clearImage = /path/to/clear/image
function writeCheck(name, value, questionText){
	var imgString = '<IMG SRC="' + clearImage + '" NAME="' + name + '">';
	var chkString = '<INPUT TYPE="CHECKBOX" NAME="' + name + '"'
	chkString += ' VALUE="' + value + '">';
	
	document.write('<TR><TD VALIGN="top">' + imgString + '</TD>');
	document.write('<TD VALIGN="top">'+ chkString + '</TD>');
	document.write('<TD WIDTH="100%" class="text_area">' + questionText + '</TD></TR>\n');
}

// writeDrop makes a full radiobutton question
// args:
//    preDrop   = text to go before dropdown box
//    postDrop  = text to go before dropdown box
//    autoCheck = [optional] if present, check answer onChange with this named function
// expects:
//    clearImage = /path/to/clear/image
//    called in between a <TABLE> and </TABLE> (it generates table rows)
function writeDrop(preDrop, postDrop, cName, cIndex, cAnswers, checkFunc){
	var imgString = '<IMG SRC="' + clearImage + '" NAME="' + cName + '">';
	
	document.write('<TR><TD VALIGN="top">' + imgString + '</TD>');
	document.write('<TD WIDTH="100%">' + preDrop);
	if(arguments.length > 5){
		mkInstantDrop(cName, cIndex, cAnswers, checkFunk);
	}else{
		mkDrop(cName, cIndex, cAnswers);
	}
	document.write(postDrop + '</TD></TR>\n');
}

// writeDropCheck makes a table row to match writeDrop()'s questions
//
// checkFunc should be a full function call, usually something like
//  'checkDrop(this.form)' or 'bigWrapper(checkDrop, this.form)'
//
// align can be
//  'left"><img src="images/spacer.gif" width="3'
// to put a 3px spacer in on the left...
// align can be prefixed with "nospan-" to line up with the question column
//  for example, align = 'nospan-left' will line up the submit button with
//  the left side of the questions and put an empty cell on the left.
//
function writeDropCheck(val, align, checkFunc, printReset){
	if(arguments.length < 4){
		var printReset = true;
	}else if(arguments[3] == 'false'){
		printReset = false;
	}
	if(arguments.length < 3 || arguments[2] == ''){
		var checkFunc = 'checkDrop';
	}
	if(arguments.length < 2 || arguments[1] == ''){
		var align = 'center';
	}
	if(arguments.length < 1 || arguments[0] == ''){
		var val = 'check';
	}
	
	var spaNdex = align.indexOf('nospan-');
	if(spaNdex == -1){		
		document.write('<tr><td colspan="2" align="' + align + '">');
	}else if(spaNdex == 0){
		align = align.substring(7, align.length);
		document.write('<tr><td><img src="'+clearImage+'"></td><td align="' + align + '">');
	}else{
		alert('error in call to writeDropCheck() - align = "' + align + "'.");
	}
	document.write('<input type="button" value="' + val + '" onClick="'+ checkFunc + '">');
	document.write('&nbsp;');
	if(printReset){
		document.write('<input type="reset" value="Reset" onClick="clearImages(this.form)">');
	}
	document.write('</td></tr>\n');
}

// writeImage makes an image to go with a question
// the first arg is the name of the form element name
//  (and therefore the name of the image)
// the optional second arg is anhy arbitrary attributes
//  that should be added to the img tag.  For example,
//  style could be "align='right'" (without the "s)
function writeImage(daName, style){
	daName = daName + '"'
	if(arguments.length > 1){
		daName = daName + ' ' + style;
	}
	var imgString = '<IMG SRC="' + clearImage + '" NAME="' + daName + '>';
	document.write(imgString);
}

// clearImages walks a form and sets all of the images back to the
//  clearImage image.  At least, all of the images associated with
//  a checkbox, dropdown, or radio button...
function clearImages(daForm){
	var daImage = new Image();
	for(var i=0;i<daForm.elements.length;i++){
		daElem = daForm.elements[i];
		if(daElem.type == "select-one" ||
		   daElem.type == "checkbox" ||
		   daElem.type == "text"){
			daImage = getImage(daElem.name);
			daImage.src = clearImage;
		}else if(daElem.type == "radio"){
			daImage = getImage(daElem.name + "-" + daElem.value);
			daImage.src = clearImage;
		}
	}

}

// trimSpace deletes leading and trailing spaces
function trimSpace(daStr){
	// insert a check for regex support somewhere in here?
	// surely the regexps would be a better solution...
	//
	//daStr = daStr.replace('/^\s*/g', '');
	//daStr = daStr.replace('/\s*$/g', '');
	//
	//clean the front up
	while(daStr.charAt(0) == ' '){
		daStr = daStr.substring(1, daStr.length);
	}
	// clean the end up
	while(daStr.charAt(daStr.length - 1) == ' '){
		daStr = daStr.substring(0, daStr.length - 1);
	}
	return daStr;
}
