// Variable to keep track of stylesheet rules added and removed. Used as a stack
var newRules = new Array();
var newRulesIndices = new Array();

function trim(str) {

    var r = /^\s*(.*\S)\s*$/;
    r.exec( str );
    return (typeof( RegExp.$1 ) != "undefined") ? RegExp.$1 : "";
};

function normalize(str) {
	var s = trim(str);
	s = s.replace(/[\n\t]/g, " "); // replace newlines and tabs with spaces
	s = s.replace(/[ ][ ]+/g, " "); // remove repeated spaces
	return s;
}

function escapeRegExpCharsOtherThanPipes(str) {
	var s = str.replace(/\*/g,"\\*");
	s = s.replace(/\$/g,"\\$");
	s = s.replace(/\^/g,"\\^");
	s = s.replace(/\[/g,"\\[");
	s = s.replace(/\]/g,"\\]");
	s = s.replace(/\+/g,"\\+");
	s = s.replace(/\./g,"\\.");
	s = s.replace(/\&/g,"\\&");
	s = s.replace(/\"/g,"\\\"");
	s = s.replace(/\'/g,"\\'");
	s = s.replace(/\{/g,"\\{");
	s = s.replace(/\}/g,"\\}");
	s = s.replace(/\?/g,"\\?");
	s = s.replace(/\(/g,"\\(");
	s = s.replace(/\)/g,"\\)");	
	return s;
}

function processValue(groupName, valueName, groupReport, mode) {

    var answer = this.form[valueName+"ans"];
    var shouldBeChecked = (answer && answer.value == "on") ? true : false;
    var status; 
    var altInfo = "";

	// Fill in the blank
    if( this.form[valueName+"type"] && this.form[valueName+"type"].value == "text" ) {
        var value = trim(this.form[valueName].value);
        if( value ) {
            groupReport.isComplete = true;

            var answer = "";
            if( this.form[valueName+"ans"] )
                answer = trim(this.form[valueName+"ans"].value);
			
			// normalize whitespace
			answer = normalize(answer);
			answer = answer.replace(/[ ][|][ ]|[ ][|]|[|][ ]/g, "|"); // take out spaces before and after pipes
			
			altInfo = "Answer: "+answer;   // alt text for fill in the blank answers
		
			/* answers must be in the format:
				answer_a | answer_b | answer_c | ... (for multiple answers)
				answer (for a single answer)
			*/

			// replace pipes with "or" for readability
			altInfo = altInfo.replace(/[|]/g, " or ");
			
			// Add something to handle case sensitive answers
			// Added a new hidden input field named case. If it exists, the answer is 
			// case sensitive. If not, it is not so convert both answer and value to lowerCase. 
            var realAnswer;
			var realValue;
            if( this.form[valueName+"case"] ) { 
				realAnswer = answer;
				realValue = value;
			} else {
				realAnswer = answer.toLowerCase();
				realValue = value.toLowerCase();
			}

			// prepare strings for test
			realAnswer = escapeRegExpCharsOtherThanPipes( realAnswer );			
			realValue = normalize( realValue );
			
			realAnswer = "^(" + realAnswer + ")$";
			var answerExp = new RegExp( realAnswer );
            status = ( answerExp.test( realValue ) ) ? "correct" : "incorrect";

        } else {
            status = "incomplete";
        }
    } else {
        // Not fill in the blank
        var input = this.form[groupName];
       
        if( typeof(input.length) != "undefined" ) {

            var inputArray = (typeof(this.form[groupName].options) != "undefined")
                              ? this.form[groupName].options : input;
        
            for( var i = 0; i < inputArray.length; i++ ) {

                if( inputArray[i].value == valueName ) {
                    input = inputArray[i];
                    break;
                }
            }
        }
   
        if( typeof(input.checked) != "undefined" )
            isOn = input.checked;
        else {
			// This is for dropdown question
            isOn = input.selected;
            if( shouldBeChecked )
                groupReport.altInfo = "Answer: "+input.text;
        }
  
        if( isOn || groupReport.selectionType == "Multiple" )
            groupReport.isComplete = true;

        if( shouldBeChecked  )
            status = isOn ? "correctly_checked" : "missed";
        else    
            status = isOn ? "incorrectly_checked" : "correctly_unchecked";
    }

	var score = 1;
    
    if( groupReport.selectionType == "Multiple"
         ||
        score > groupReport.maxScore )
        groupReport.maxScore += score;
   
    if( status == "correctly_checked" || status == "correct" 
         ||
        (status == "correctly_unchecked" && groupReport.selectionType == "Multiple")) {
        groupReport.score += score;
    }
 
    var gif = document[valueName+"gif"];
  
    if( gif ) {
  
        if( altInfo )
            gif.alt = altInfo;
        if( mode == "clearinfo" ) {
            gif.alt = "";
           
            gif.src = this.form["resourceURL"].value+"ansblank.gif";
        } else
            if( mode == "completeness" ) {
                 
                if( status == "incomplete" )
                
                    gif.src = this.form["resourceURL"].value+"ansincmp.gif";
                else
                
                    gif.src = this.form["resourceURL"].value+"ansblank.gif";
                     
            } else
                switch( status ) {
                    case "correctly_unchecked":
                        if( groupReport.selectionType == "Multiple" ) {
                            gif.alt = "Good";
                            gif.src = this.form["resourceURL"].value+"ansright.gif";
                        }
                        break;            
                    case "correct":
                    case "correctly_checked":
                        gif.alt = "Good";
                        gif.src = this.form["resourceURL"].value+"ansright.gif";
                        break;            
                    case "missed":
                        gif.alt = "This is the correct answer";
                        gif.src = this.form["resourceURL"].value+"ansmissd.gif";
                        break;            
                    case "incorrectly_checked":
                    case "incorrect":
                        if( !altInfo )
                            gif.alt = "Incorrect answer";
                        gif.src = this.form["resourceURL"].value+"answrong.gif";
                        break;            
                    case "incomplete":
                        gif.alt = "Please provide an answer";
                        gif.src = this.form["resourceURL"].value+"ansincmp.gif";
                        break;            
                    default:
                        gif.src = this.form["resourceURL"].value+"ansblank.gif";
                }
    }
 
    switch( status ) {
        case "correctly_unchecked":
        case "correct":
        case "correctly_checked":
            return "correct";    
       default:
            return "incorrect";    
    }
}

function processGroup(groupName, questionReport, mode) {

	// valueNames has a minimum value of 1 for each question type
    var valueNames = this.form[groupName+"values"].value.split(" ");
    var status;

    var selectionType = "exclusive";
    if( this.form[groupName+"selection"]
         &&
        this.form[groupName+"selection"].value == "Multiple" )
        selectionType = "Multiple";    

    status = "correct"; // Correct is the assumed default
    var groupReport = new GroupReport(selectionType);
    for( var i = 0; i < valueNames.length; i++ ) {
        var valueName = valueNames[i];
        var valueStatus = this.processValue(groupName, "value"+valueName, groupReport, mode);
        if( valueStatus == "incorrect" )
            status = valueStatus;
    }
    if( !groupReport.isComplete )
        questionReport.isComplete = false;

    var scoreWeight = 1;
    
    var score;
    var maxScore;
    
    score = groupReport.score * scoreWeight;
    maxScore = groupReport.maxScore * scoreWeight;
    

    questionReport.maxScore += maxScore;
    questionReport.score += score;

    if( status == "incorrect" )
        questionReport.status = status;
 
    var gif = document[groupName+"gif"];
    
    if( gif ) {
    
        if( groupReport.altInfo && groupReport.isComplete) // added a check; only show answer if the user entered something
            gif.alt = groupReport.altInfo;
         
        if( mode == "clearinfo" ) {
            gif.alt = "";
            gif.src = this.form["resourceURL"].value+"ansblank.gif";
        
        } else
            
            if( mode == "completeness" ) {
                if( groupReport.isComplete )
                    gif.src = this.form["resourceURL"].value+"ansblank.gif";
                else
                    gif.src = this.form["resourceURL"].value+"ansincmp.gif";
            } else {
                if( !groupReport.isComplete ) {
                    gif.src = this.form["resourceURL"].value+"ansincmp.gif";
					gif.alt = "Please provide an answer"; // added this text
				} else
                    if( status == "correct" )
                        gif.src = this.form["resourceURL"].value+"ansright.gif";
                    else
                        gif.src = this.form["resourceURL"].value+"answrong.gif";
            }
    }
 
    return status;
}

function processQuestion(questionNum, quizReport, mode) {
	
	// Every type of question has a groupNames minimum length 1; only matchset is greater than 1
	var groupNames = this.form["question"+questionNum+"groups"].value.split(" ");
    var status;
    var altInfo = "";

    var questionReport = null;
    if( quizReport ) {
        var questionNumStr;
        if( this.form["question"+questionNum+"numberstr"] )
            questionNumStr = this.form["question"+questionNum+"numberstr"].value;
        else
            questionNumStr = "Question " + questionNum;
        questionReport = new QuestionReport(questionNumStr);
    }

	// Status is correct by default
    status = "correct";
	// This gets called for all question types since there is always a groupNames set
    for( var i = 0; i < groupNames.length; i++ ) {
          
        var groupStatus = this.processGroup("group"+groupNames[i],questionReport,mode);
        if( groupStatus == "incorrect" )
            status = groupStatus;
        
    }
  
    if( quizReport ) {

        var scoreWeight = 1;
    
        var score;
        var maxScore;

        score = questionReport.score * scoreWeight;
        maxScore = questionReport.maxScore * scoreWeight;
        

        altInfo = "Score: "+score+" of "+maxScore;
	
        quizReport.questionReports[quizReport.questionReports.length] = questionReport;

        quizReport.totalMaxScore += maxScore;
        quizReport.totalScore += score;
    }

    var gif = document["question"+questionNum+"gif"];
   
    if( gif ) {
    
        if( altInfo )
            gif.alt = altInfo;
        if( mode == "clearinfo" ) {
            gif.alt = "";
            gif.src = this.form["resourceURL"].value+"blank.gif";
        } else
           
            if( mode == "completeness" ) {
                 
                if( questionReport.isComplete )
                    gif.src = this.form["resourceURL"].value+"complete.gif";
                else
                    gif.src = this.form["resourceURL"].value+"incomplt.gif";
                 
            } else {
                if( !questionReport.isComplete )
                    gif.src = this.form["resourceURL"].value+"incomplt.gif";
                else
                    if( status == "correct" )
                        gif.src = this.form["resourceURL"].value+"correct.gif";
                    else
                        gif.src = this.form["resourceURL"].value+"incorrct.gif";
            }
    }

    return status;
}

function processQuestions(quizReport, mode) {

    // Get the number of questions to process
	var questionNum = this.form["questionnum"].value;
    
	// Hide quiz responses by modifying the stylesheet unless the user has chosen to mark
	// using showAnswer and clearAnswers
    if( questionNum ) {
        clearAnswers();
		for( var i = 0; i < questionNum; i++ ) {
        	// This returns the status for each question
            result = this.processQuestion(i+1, quizReport, mode);
			if ( mode == "mark" || mode == "markexercise" ) {
				// If an answer was provided, then show the question response
				addAnswer(result, i+1, quizReport.questionReports[i].isComplete);
			}
        } 
		if ( mode == "mark" || mode == "markexercise" ) showAnswers();
		// else clearAnswers();
    }
}

// Clear the answers displayed if not in mark mode
function clearAnswers() {
    // Netscape 4 does not support document.styleSheets. It uses layers; might try document.layers[0].
    // IE5.0 does not support Array as a stack using push and pop
    // Opera does not support document.styleSheets
    if (is_nav4 | is_ie5 | is_opera) return;

    // Need to remove all styles from sheet.rules.length (sheet.rules.length - newRules.length)
    var sheet = document.styleSheets[0];
    // alert("rules length is: "+newRulesIndices.length);
    while (newRulesIndices.length > 0) {
        if (is_nav6up) {
            //alert("About to clear. Sheet length is: "+sheet.cssRules.length);
            sheet.deleteRule(newRulesIndices.pop());
            //alert("Removed rule. Now sheet length is: " + sheet.cssRules.length);
        } else {
            //alert("About to clear. Sheet length is: "+sheet.rules.length);
            sheet.removeRule(newRulesIndices.pop());
            //alert("Removed rule. Now sheet length is: " + sheet.rules.length);
        }
    }
}

// Show quiz responses by modifying the stylesheet based on the newRules list
function showAnswers(result, questionNum, complete) {
    // Netscape 4 does not support document.styleSheets. It uses layers; might try document.layers[0].
    // IE5.0 does not support Array as a stack using push and pop
    // Opera does not support document.styleSheets
    if (is_nav4 | is_ie5 | is_opera) return;

    var sheet = document.styleSheets[0];
    while (newRules.length > 0) {
        // Remove the style from the list as we add it to the stylesheet
        if (is_nav6up) {
            //alert("sheet length before add: "+sheet.cssRules.length);
            sheet.insertRule(newRules.pop() + " {display: inline;}", sheet.cssRules.length); 
            //alert("sheet length after add: "+sheet.cssRules.length);
            newRulesIndices.push(sheet.cssRules.length-1); // add the index to a list
        } else { 
            //alert("sheet length before add: "+sheet.rules.length);
            sheet.addRule(newRules.pop(), "display:inline;"); 
            //alert("sheet length after add: "+sheet.rules.length);
            newRulesIndices.push(sheet.rules.length-1); // add the index to a list
        }
    }
}

// Add quiz responses to the newRules list based on the status
function addAnswer(result, questionNum, complete) {
    // Netscape 4 does not support document.styleSheets. It uses layers; might try document.layers[0].
    // IE5.0 does not support Array as a stack using push and pop
    // Opera does not support document.styleSheets
    if (is_nav4 | is_ie5 | is_opera) return;

    var sheet = document.styleSheets[0];
    var qNum = "Q"+questionNum;

    // Check if answer is complete
    if (complete) {	// Result is either correct or incorrect
        if (result == "correct")
            styleName = ".QuizResponseCorrect#"+qNum;
        else //result == "incorrect" {
            styleName = ".QuizResponseIncorrect#"+qNum;
        newRules.push(styleName); // add rule to stack
    }  
}

function processQuiz(mode) {

    var quizReport = new QuizReport(this);
    this.processQuestions(quizReport,mode);
    
    if( mode != "clearinfo" && mode != "markexercise" ) 
        quizReport.displayReport(mode);

}

function TH(content) {
    return '<TH width="10">'+content+'</TH>';
}

function displayReport(mode) {

    var reportWindow = window.open('','ReportWindow','height=450,width=400,scrollbars=yes')

    reportWindow.document.write('<HTML>');
    reportWindow.document.write('<HEAD><TITLE>Quiz Summary</TITLE>');
    
    reportWindow.document.write('</HEAD><BODY><font face="Trebuchet MS, Arial, Helvetica, Sans-serif" size="8"><H2>Quiz Summary</H2>')

	reportWindow.document.write('<TABLE WIDTH="90%" CELLPADDING="4">')

    reportWindow.document.write('<TR align="left">');
    if( mode == "completeness" )
        reportWindow.document.write(TH("Question")+TH("Status"));
    else
        reportWindow.document.write(TH("Question")+TH("Status")+TH("Score")+TH("Max")+'</TR>');

    for( var i = 0; i < this.questionReports.length; i++ ) {
        var qR = this.questionReports[i];
        reportWindow.document.write('<TR><TD>'+qR.numberStr+'</TD>' );

        var gifERL;
        if( mode == "completeness" ) {
            if( qR.isComplete ) 
				gifERL = this.formHandler.form["resourceURL"].value+"complete.gif";  // gifERL = "<FONT COLOR = 'GREEN'>COMPLETE</FONT>";
            else
                gifERL = this.formHandler.form["resourceURL"].value+"incomplt.gif"; //"<FONT COLOR = 'BLACK'>INCOMPLETE</FONT>";
        } else
            if( !qR.isComplete )
                gifERL = this.formHandler.form["resourceURL"].value+"incomplt.gif"; //"<FONT COLOR = 'BLACK'>INCOMPLETE</FONT>";
            else
                if( qR.status == "correct" )
                    gifERL = this.formHandler.form["resourceURL"].value+"correct.gif"; //"<FONT COLOR = 'GREEN'>CORRECT</FONT>"; 
                else
                    gifERL = this.formHandler.form["resourceURL"].value+"incorrct.gif"; //"<FONT COLOR = 'RED'>INCORRECT</FONT>"; 
		// Create the image string from the gif
		gifERL = "<img src='" + gifERL + "'/>";
        reportWindow.document.write('<TD>' + gifERL + '</TD>');
        if( mode != "completeness" ) {
           reportWindow.document.write('<TD ALIGN="right">'+qR.score+'</TD>');
           reportWindow.document.write('<TD ALIGN="right">'+qR.maxScore+'</TD>');
        }
        reportWindow.document.write('</TR>');
    }

    if( mode != "completeness" ) {
        var pcent = Math.round((1000 * this.totalScore) / this.totalMaxScore) / 10;

        reportWindow.document.write
         ('<TR><TD COLSPAN="4">Total score: '
          +this.totalScore+' of '+this.totalMaxScore+' ('+pcent+'%)</TD></TR>');
    }


    reportWindow.document.write('</TABLE>');

    reportWindow.document.write('</font></BODY></HTML>');
    reportWindow.document.close();
    reportWindow.focus();
}

function QuizReport(formHandler) {
    this.formHandler     = formHandler;
    this.questionReports = new Array();
    this.displayReport   = displayReport;
    this.totalMaxScore   = 0;
    this.totalScore      = 0;
}

function QuestionReport(numberStr) {
    this.numberStr  = numberStr;
    this.maxScore   = 0;
    this.score      = 0;
    this.status     = "correct";
    this.isComplete = true;
}

function GroupReport(selectionType) {
    this.selectionType = selectionType;
    this.score         = 0;
    this.maxScore      = 0;
    this.status        = "correct";
    this.isComplete    = false;
    this.altInfo       = "";
}

function FormHandler(form) {

    this.form             = form;
    this.processValue     = processValue;
    this.processGroup     = processGroup;
    this.processQuestion  = processQuestion;
    this.processQuestions = processQuestions;
    this.processQuiz      = processQuiz;
}
