// $Id: spellcheck.js,v 1.1.2.2 2005-12-12 10:50:01 thox Exp $

if (isJsEnabled()) {
  addLoadEvent(spellcheckerAutoAttach);
}

/**
 * Attach spellchecking to all textareas on the current page
 */
function spellcheckerAutoAttach() {
  var textAreas = document.getElementsByTagName('textarea');
  for (i = 0; i < textAreas.length; i++) {
    temp = new spellchecker;
    temp.setup(textAreas[i]);
  }

  if (document.onclick) {
    var oldOnclick = document.onclick;
    document.onclick = function(e) {
      spellcheckerHandleClick(e);
      oldOnclick(e);
    }
  } else {
    document.onclick = spellcheckerHandleClick;
  }
}

/**
 * Handles onclick events on the page to hide the suggestions popup
 */
function spellcheckerHandleClick(e) {
  var node = window.event ? window.event.srcElement : e.target;
  switch (node.className) {
    case 'spellcheck-correction':
    case 'spellcheck-mistake':
      break;
    default:
      removeNode(document.getElementById('spellcheck-suggestions'));
  }
}

/**
 * spellchecker object
 */
spellchecker = function () {
  /* Intentionally empty */
}


/**
 * Initialises a spellchecker object
 */
spellchecker.prototype.setup = function (textArea) {
  this.highlighted = false;
  this.textArea = textArea;
  this.corrections = [];

  var sc = this;
  this.para = document.createElement('p');
  this.para.className = 'spellcheck-trigger';
  this.trigger = document.createElement('a');
  this.trigger.href = '#';
  this.trigger.innerHTML = 'Check spelling';
  this.trigger.onclick = function() { sc.check(); return false; };
  this.para.appendChild(this.trigger);
  this.textArea.parentNode.insertBefore(this.para, this.textArea.nextSibling);
}

/**
 * Performs the actual spelling check, sending results to spellchecker.callback
 */
spellchecker.prototype.check = function () {
  if (this.textArea.value.length == 0) {
    return this.statusMessage('Nothing to check');
  }
  this.trigger.innerHTML = 'Checking...';
  this.trigger.onclick = function() { return false; };
  var base_url = document.getElementsByTagName('base')[0].href;
  HTTPPost(base_url + '?q=spellcheck', this.textArea.value, this.callback, this);
}

/**
 * Converts the spellchecking DIV back to a textarea
 */
spellchecker.prototype.resume = function () {
  var sc = this;
  this.trigger.innerHTML = "Check spelling";
  this.trigger.onclick = function() { sc.check(); return false; };

  if (this.corrections.length > 0) {
    var correctedString = '';
    var offset = 0;
    var words = this.div.getElementsByTagName('a');
    for (i = 0; correction = words[i]; i++) {
      correction = this.corrections[i];
      correctedString += this.textArea.value.substr(offset, correction.stringStart - offset);
      correctedString += words[i].innerHTML;
      offset = correction.stringStart + correction.stringLength;
    }
    if (offset < this.textArea.value.length) {
      correctedString += this.textArea.value.substr(offset);
    }
    this.textArea.value = correctedString;
    removeNode(this.div);
    this.div = null;
    this.textArea.style.display = 'block';
    this.corrections = [];
  }
}

/**
 * Handles HTTP errors and sends XML to spellchecker.parseResponse
 */
spellchecker.prototype.callback = function (html, http, spellcheckerInstance) {
  if (http.status != 200) {
    spellcheckerInstance.statusMessage('An HTTP error occured');
    spellcheckerInstance.resume();
    return;
  }
  spellcheckerInstance.parseResponse(http.responseXML);
}

/**
 * Displays a status message on the page for 2 seconds
 */
spellchecker.prototype.statusMessage = function (statusString) {
  if (!this.statusElement || !this.statusElement.parentNode) {
    this.statusElement = document.createElement('span');
    this.statusElement.className = 'spellcheck-status';
    this.para.appendChild(this.statusElement);
  }
  this.statusElement.innerHTML = ' - ' + statusString;
  var temp = this.statusElement;
  setTimeout(function() { removeNode(temp); }, 2000);
}

/**
 * Parses the XML response from Google
 */
spellchecker.prototype.parseResponse = function (xml) {
  if (!xml || !xml.documentElement) {
    return this.statusMessage('An XML error occured');
  }

  var correctionNodes = xml.documentElement.getElementsByTagName('c');
  var sc = this;

  if (correctionNodes.length > 0) {
    this.trigger.innerHTML = "Resume editing";
    this.trigger.onclick = function() { sc.resume(); return false; };

    var i = 0;
    for (i = 0; i < correctionNodes.length; i++) {
      this.corrections[i] = {
        'stringStart'   : parseInt(correctionNodes[i].getAttribute('o')),
        'stringLength'  : parseInt(correctionNodes[i].getAttribute('l')),
        'suggestions'   : correctionNodes[i].hasChildNodes() ? correctionNodes[i].firstChild.nodeValue.split('\t') : []
      };
    }
    this.statusMessage('Found ' + correctionNodes.length + ' possible mistakes');
    this.displayCorrectionDiv();
  }
  else {
    this.statusMessage('No mistakes found');
    this.corrections = [];
    this.resume();
  }
  return this.corrections;
}

/**
 * Converts the current textarea into an interactive spell checking DIV
 */
spellchecker.prototype.displayCorrectionDiv = function () {
  this.div = document.createElement('div');
  this.div.id = 'spellcheck';
  this.div.style.overflow = 'auto';
  this.div.style.height = this.textArea.offsetHeight + 'px';
  this.div.style.width = this.textArea.offsetWidth + 'px';

  var offset = 0;
  var sc = this;

  for (var i = 0; correction = this.corrections[i]; i++) {
    span = document.createElement('span');
    span.innerHTML = this.checkPlain(this.textArea.value.substr(offset, correction.stringStart - offset));
    this.div.appendChild(span);

    wordLink = document.createElement('a');
    wordLink.href = '#';
    wordLink.innerHTML = this.textArea.value.substr(correction.stringStart, correction.stringLength);
    wordLink.className = 'spellcheck-mistake';
    wordLink.onclick = function() { sc.clickWord(this); return false; };
    wordLink.suggestions = correction.suggestions;
    wordLink.sc = this;
    this.div.appendChild(wordLink);

    offset = correction.stringStart + correction.stringLength;
  }
  if (offset < this.textArea.value.length) {
    span = document.createElement('span');
    span.innerHTML = this.checkPlain(this.textArea.value.substr(offset));
    this.div.appendChild(span);
  }

  this.textArea.parentNode.insertBefore(this.div, this.textArea);
  this.textArea.style.display = 'none';
}

/**
 * Handles the user clicking on a highlighted word
 */
spellchecker.prototype.clickWord = function (word) {
  var div = document.getElementById('spellcheck-suggestions');
  if (!div) {
    div = document.createElement('div');
    div.id = 'spellcheck-suggestions';
  }
  while (div.hasChildNodes()) {
    removeNode(div.firstChild);
  }
  var ul = document.createElement('ul');
  var sc = this;

  word.suggestions.push('Edit...');

  for (var i = 0; i < word.suggestions.length; i++) {
    li = document.createElement('li');
    li.innerHTML = word.suggestions[i];
    li.onmousedown = function() { sc.clickSuggestion(this, word); };
    li.onmouseover = function() { sc.highlight(this, word); };
    li.onmouseout  = function() { sc.unhighlight(this, word); };
    if (word.suggestions[i] == 'Edit...') {
      li.className = 'spellcheck-edit';
    }
    ul.appendChild(li);
  }

  word.suggestions.pop();

  var pos = absolutePosition(word);
  div.style.top   = (pos.y + word.offsetHeight) +'px';
  div.style.left  = pos.x +'px';

  div.appendChild(ul);
  document.getElementsByTagName('body')[0].appendChild(div);
}

/**
 * Handles highlighting of a word suggestion
 */
spellchecker.prototype.highlight = function (node) {
  removeClass(this.highlighted, 'selected');
  addClass(node, 'selected');
  this.highlighted = node;
}

/**
 * Removes highlighting of suggestions
 */
spellchecker.prototype.unhighlight = function (node) {
  removeClass(node, 'selected');
  this.highlighted = false;
}

/**
 * Handles clicking of spellcheck suggestions
 */
spellchecker.prototype.clickSuggestion = function(suggestion, word) {
  if (suggestion.innerHTML == 'Edit...') {
    var input = document.createElement('input');
    input.value = word.innerHTML;
    input.style.width = (word.offsetWidth + 10) + 'px';
    input.onchange = function () { word.innerHTML = this.value; };
    word.parentNode.insertBefore(input, word);
    word.style.display = 'none';
  }
  else {
    word.innerHTML = suggestion.innerHTML;
    word.className = 'spellcheck-correction';
  }
  removeNode(document.getElementById('spellcheck-suggestions'));
}

/**
 * Converts some characters to HTML
 */
spellchecker.prototype.checkPlain = function (string) {
  string = eregReplace("&", "&amp;", string);
  string = eregReplace(">", "&gt;", string);
  string = eregReplace("<", "&lt;", string);
  string = eregReplace("\n", "<br />", string);
  if (string == ' ') {
    string = '&nbsp;';
  }
  return string;
}
