/* Copyright (C) 2004-2009 Brailcom, o.p.s.
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307
 * USA */

// General use functions

function count(array, value) {
   // Return the count of elements having the given value in the array.
   var count = 0;
   for (i=0; i<array.length; i++)
      if (array[i] == value) count++;
   return count;
}

function event_key(e) {
   // Return a textual representation of a pressed key.
   var code = document.all ? e.keyCode : e.which;
   var map = [];
   map[8] = 'Backspace';
   map[13] = 'Enter';
   map[32] = 'Space';
   map[10] = 'Enter';
   var key;
   if (map[code] != null) {
      var cap = code >= 65 && code <= 90;
      var modifiers = '';
      if (document.all || document.getElementById) {
	 modifiers += e.ctrlKey ? 'Ctrl-' : '';
	 modifiers += e.altKey ? 'Alt-' : '';
	 modifiers += e.shiftKey && !cap ? 'Shift-' : '';
      } else if (document.layers) {
	 modifiers += e.modifiers & Event.CONTROL_MASK ? 'Ctrl-' : '';
	 modifiers += e.modifiers & Event.ALT_MASK ? 'Alt-' : '';
	 modifiers += e.modifiers & Event.SHIFT_MASK && !cap ? 'Shift-' : '';
      }
      key = modifiers + map[code]
   } else {
      key = String.fromCharCode(code);
   }
   //alert(modifiers+key +' ('+code+')');
   return key;
}

function event_target(e) {
   var target;
   if (e.target) target = e.target;
   else if (e.srcElement) target = e.srcElement;
   if (target.nodeType == 3) // defeat Safari bug
      target = targ.parentNode;
   return target;
}

function highlight(field, start, end) {
  if (field.setSelectionRange) {
    field.focus();
    field.setSelectionRange(start, end);
  }
  else if (field.createTextRange) {
    var range = field.createTextRange();
    range.collapse(true);
    range.moveEnd('character', end);
    range.moveStart('character', start);
    range.select();
  }
}

function simulate_click_on_keypress(e) {
   if (document.all) e = window.event;
   var key = event_key(e);
   var target = event_target(e);
   if (key == 'Enter' || key == 'Space') target.onclick();
   return true;
}

/* Given a node name and a class name, return the first
   child element of _parent_ witch CSS class name exactly
   _class_name_ */
function get_child_element_by_class(parent, class_name) {
  var children = parent.childNodes;
  for (var i=0; i < children.length; i++) {
    if (children[i].className == class_name) {
      return children[i];
      break;
    }
  }
  return false;
}

String.prototype.normalize_space = function() {
  // Replace repeated spaces, newlines and tabs with a single space
  return this.replace(/^\s*|\s(?=\s)|\s*$/g, "");
}

// Exercises

//=============================================================================
// Generic exercise form handler class.

function Handler() {
   if (document.captureEvents) document.captureEvents(Event.KEYPRESS);
}

Handler.prototype.init = function(form, answers, responses, messages) {
   this._form = form;
   this._answers = answers;
   this._responses = responses;
   this._messages = messages;
   this._results = new Array(answers.length);
   this._first_attempt = new Array(answers.length);
   this._fields = new Array();
   this._last_answer_index = 0;
   for (i=0; i < this._form.elements.length; i++) {
      var field = this._form.elements[i];
      if (this._recognize_field(field)) {
	 this._init_field(field);
	 this._fields.push(field);
      }
   }

   /* Hide transcript and show show/hide button */
   transcript_section = get_child_element_by_class(this._form, "transcript-section");
   if (transcript_section != false){
       transcript_section.style.display="none";
       media_controls = get_child_element_by_class(this._form, "media-controls recording");
       if (media_controls != false){
	   transcript_show_hide_button = get_child_element_by_class(
			    media_controls, "button transcript-button");
	   transcript_show_hide_button.style.display="inline";
       }
   }
}

Handler.prototype.response = function(selector) {
   var array = this._responses[selector];
   var n = Math.floor(Math.random() * array.length);
   return array[n];
}

Handler.prototype.msg = function(text) {
   if (text in this._messages) return this._messages[text];
   else return text;
}

Handler.prototype.set_answer = function(i, value) {
   this._fields[i].value = value;
}

Handler.prototype.get_value = function(i) {
   return this._fields[i].value;
}


/* Show/hide transcript and change text on the show/hide button  */
Handler.prototype.toggle_transcript = function(button) {
    transcript_section = get_child_element_by_class(this._form, "transcript-section");						       									    
    if ((transcript_section.style.display == "none") || (transcript_section.style.display == "")){
	transcript_section.style.display = "block";
	button.value = this.msg("Hide transcript");
    }else{
	transcript_section.style.display = "none";
	button.value = this.msg("Show transcript");
    }

    return true;
}

Handler.prototype._error_handler = function(field) {
   field.focus();
}

Handler.prototype._eval_answer = function(value, i) {
   return value == this._answers[i];
}

Handler.prototype._eval_answers = function() {
   for (var i=0; i < this._answers.length; i++) {
      var value = this.get_value(i);
      if (value != '' && value != null) {
	 this._results[i] = (this._eval_answer(value, i) ? 1:-1);
	 if (this._first_attempt[i] == null)
	    this._first_attempt[i] = this._results[i];
      } else {
	 this._results[i] = null;
      }
   }
   this.display_results();
}

Handler.prototype.eval_answer = function(field) {
   this._eval_answers();
   var i = field.answer_index;
   var result = this._results[i];
   if (result != null) {
      play_media(this.response(result == 1 ? 'correct':'incorrect'));
      if (result == 1) {
	 // if (i < this._fields.length)
	 //    This doesn't work in MultipleChoiceQuestions (answer index is not
	 //    a field index).  But we probably don't want it anyway...
	 //    this._fields[i+1].focus();
      } else {
	 this._error_handler(field);
      }
   }
}

Handler.prototype.evaluate = function() {
   this._eval_answers();
   for (var i=0; i < this._fields.length; i++) {
      var field = this._fields[i];
      if (this._results[field.answer_index] != 1) {
	 this._error_handler(field);
	 break;
      }
   }
   if (this._fields.length > 1) {
      percentage = this.first_attempt_percentage()
      if      (percentage < 50)  selector='f0-49';
      else if (percentage < 70)  selector='f50-69';
      else if (percentage < 85)  selector='f70-84';
      else if (percentage < 100) selector='f85-99';
      else selector='f100';
   } else {
      selector = this.correct() ? 'correct':'incorrect';
   }
   play_media(this.response(selector));
}

Handler.prototype.correct = function() {
   return count(this._results, 1);
}

Handler.prototype.incorrect = function() {
   return count(this._results, -1);
}

Handler.prototype.percentage = function() {
   return Math.round(100 * this.correct() / this._answers.length)
}

Handler.prototype.first_attempt_correct = function() {
   return count(this._first_attempt, 1);
}

Handler.prototype.first_attempt_percentage = function() {
   return Math.round(100 * this.first_attempt_correct() / this._answers.length)
}

Handler.prototype.display_results = function() {
   var correct = this.correct();
   var incorrect =  this.incorrect();
   var first_attempt_correct = this.first_attempt_correct();
   this._form.answered.value = (correct+incorrect) +'/'+ this._answers.length;
   result = correct + " ("+ this.percentage() +"%)";
   if (correct != first_attempt_correct) {
      result += ", "+ first_attempt_correct +" ("+ this.first_attempt_percentage() +"%) "+
	 this.msg("on first attempt");
   }
   this._form.result.value = result;
}

Handler.prototype.fill = function() {
   for (var i=0; i < this._answers.length; i++) {
      this._fill_answer(i);
   }
   this._eval_answers();
}

Handler.prototype._fill_answer = function(i) {
      this.set_answer(i, this._answers[i]);
}

Handler.prototype.reset = function() {
   this._results = new Array(this._answers.length);
   this._first_attempt = new Array(this._answers.length);
   this.display_results();
}

//=============================================================================
// Choice based exercise handler class.

function ChoiceBasedExerciseHandler() {}
ChoiceBasedExerciseHandler.prototype = new Handler();

ChoiceBasedExerciseHandler.prototype._recognize_field = function(field) {
   return field.type == 'radio';
}

ChoiceBasedExerciseHandler.prototype._init_field = function(field) {
   if (field.name != this._last_group) {
      if (this._last_group != null) this._last_answer_index++;
      this._last_group = field.name;
   }
   field.answer_index = this._last_answer_index;
}

ChoiceBasedExerciseHandler.prototype.set_answer = function(i, value) {
   for (var n=0; n < this._fields.length; n++) {
      var field = this._fields[n];
      if (field.answer_index == i) {
	 field.checked = (field.value == value);
      }
   }
}

ChoiceBasedExerciseHandler.prototype.get_value = function(i) {
   for (var n=0; n < this._fields.length; n++) {
      var field = this._fields[n];
      if (field.answer_index == i && field.checked) {
	 return field.value;
      }
   }
   return null;
}

ChoiceBasedExerciseHandler.prototype._error_handler = function(field) {
   var i = field.answer_index;
   for (var n=0; n < this._fields.length; n++) {
      var f = this._fields[n];
      if (f.answer_index == i && f.checked) {
	 f.focus();
	 return;
      }
   }
   field.focus();
}

//=============================================================================

function SelectBasedExerciseHandler() {}
SelectBasedExerciseHandler.prototype = new Handler()

SelectBasedExerciseHandler.prototype._recognize_field = function(field) {
   return field.options != null;
}

SelectBasedExerciseHandler.prototype._init_field = function(field) {
   field.answer_index = this._last_answer_index++;
}

SelectBasedExerciseHandler.prototype.set_answer = function(i, value) {
   var field = this._fields[i];
   for (var i=0; i < field.options.length; i++) {
      var option = field.options[i];
      if (option.value == value) option.selected = true;
   }
}

//=============================================================================
// Fill-in text exercise handler class.

function FillInExerciseHandler() {}
FillInExerciseHandler.prototype = new Handler();

FillInExerciseHandler.prototype._recognize_field = function(field) {
   return ((field.type == 'text' || field.type == 'textarea') && 
	   this._last_answer_index < this._answers.length);
}

FillInExerciseHandler.prototype._init_field = function(field) {
   field.answer_index = this._last_answer_index++;
   field.onkeypress = this._handle_text_field_keypress;
   field.ondblclick = this._handle_dlbclick;
}

FillInExerciseHandler.prototype._find_answer = function(field) {
   var answers = this._answers[field.answer_index].split('|');
   var value = field.value;
   var answer_index = 0;
   var char_index = 0; // max last correct char index
   for (var i=0; i < answers.length; i++) {
      var a = answers[i];
      var j = 0;
      while (value.slice(0, j+1) == a.slice(0, j+1) && j < a.length) j++; 
      if (j > char_index) {
	 char_index = j;
	 answer_index = i;
      }
   }
   return {"answer": answers[answer_index], "index": char_index};
}

FillInExerciseHandler.prototype._error_handler = function(field) {
   var found = this._find_answer(field);
   var index = found.index
   field.focus()
   highlight(field, index, index);
}

FillInExerciseHandler.prototype._eval_answer = function(value, i) {
   var answers = this._answers[i].split('|');
   for (var j=0; j < answers.length; j++) {
       if (value.normalize_space() == answers[j]) return true;
   }
   return false;
}

FillInExerciseHandler.prototype._fill_answer = function(i) {
   var answers = this._answers[i].split('|');
   this.set_answer(i, answers[0]);
}

FillInExerciseHandler.prototype._handle_dlbclick = function(e) {
   if (document.all) e = window.event;
   var field = event_target(e);
   field.form.handler.eval_answer(field);
   return false;
}

FillInExerciseHandler.prototype._handle_text_field_keypress = function(e) {
   if (document.all) e = window.event;
   var key = event_key(e);
   var field = event_target(e);
   // 'this' does not refer to the FillInExerciseHandler instance here!
   var _this = field.form.handler;
   if (key == 'Enter') { // Eval
      _this.eval_answer(field);
      return false;
   } else if (key == 'Ctrl-Space') { // Hint
      var found = _this._find_answer(field);
      var answer = found.answer; 
      var i = found.index; 
      var val = field.value
      if (answer.length > i) {
	 field.value = answer.slice(0, i+1) + val.slice(i, val.length);
	 highlight(field, i+1, i+1);
      } else if (field.value.length > i) {
	 field.value = val.slice(0, i);
	 highlight(field, i, i);
      }
      return false;
   } 
   return _this._handle_key(key);
}

FillInExerciseHandler.prototype._handle_key = function(key) {
   return true;
}

function DictationHandler() {}
DictationHandler.prototype = new FillInExerciseHandler();

DictationHandler.prototype.init_recordings = function(recordings) {
   this._recordings = recordings;
   this._current_recording = -1;
}

DictationHandler.prototype._handle_key = function(key) {
   if (this._recordings == null) return true;
   if (key == '>' || key == '<' || key == 'Ctrl-Enter') {
      if (key == '>') {
	 if (this._current_recording >= this._recordings.length-1) return false;
	 this._current_recording++;
      }
      if (key == '<') {
	 if (this._current_recording <= 0) return false;
	 this._current_recording--;
      }
      play_media(this._recordings[this._current_recording]);
      return false;
   } 
   return true;
}

DictationHandler.prototype.display_results = function() {
   msg = this.msg(this.correct() ? 'Correct':'Error(s) found');
   this._form.result.value = msg
}
