function showJs(url, elem) {
  new Ajax.Request(url, {
    method: 'get',
    onSuccess: function(transport) {
      elem = $(elem);

      while (elem.hasChildNodes()) {
        elem.removeChild(elem.lastChild);
      }

      elem.appendChild(document.createTextNode(transport.responseText));

      highlightSyntaxJs(elem);
    }
  });
}


function toogleLineNumbers(elem) {
  $(elem).getElementsBySelector('.jsLine').each(function(item) {
    if (item.visible()) {
      // remove line number string to enable copy and pase without numbers.
      item.removeChild(item.lastChild);
    } else {
      item.appendChild(document.createTextNode(item.getAttribute('line')));
    }
    item.toggle();
  });
}

function hideLineNumbers(elem) {
  elem = $(elem);
  var first = $(elem).getElementsBySelector('.jsLine').first();
  var hasLines = first && first.textContent != "";

  if (hasLines) {
    toogleLineNumbers(elem);
  }
}

function selectCode(elem) {
  hideLineNumbers(elem);

  elem = $(elem);

  if (document.selection && document.selection.createRange) {
    var range = document.createRange();
    range.moveToElementText(elem);
    range.select();

  } else if (window.getSelection && window.getSelection().selectAllChildren) {
    window.getSelection().selectAllChildren(elem);
  }
}


function highlightSyntaxJs(elem) {
  //
  // private functions
  //

  function addResult(result, styleClass, value) {
    if (styleClass == 'br') {
      result.push(document.createElement('br'));
    } else if (styleClass) {
      var node = document.createElement('span');
      node.setAttribute('class', styleClass);
      node.appendChild(document.createTextNode(value));
      result.push(node);
    } else {
      var last = result.last();
      if (last && last.nodeType == Node.TEXT_NODE) {
        result.pop();
        result.push(document.createTextNode(last.nodeValue + value));
      } else {
        result.push(document.createTextNode(value));
      }
    }
  }

  function addLineNumber(result, number) {
    var node = document.createElement('span');
    node.setAttribute('class', 'jsLine');
    var str = "    " + number;
    var v = str.substring(str.length - 4, str.length);
    node.setAttribute('line', v);
    node.appendChild(document.createTextNode(v));

    result.push(node);
  }


  function isToken(text, i, regex) {
    var result = text.substring(i, text.length).match(regex);
    if (result) {
      return i + result[1].length;
    }

    return -1;
  }


  function isId(text, i) {
    return isToken(text, i, /^([a-zA-Z_][a-zA-Z0-9_]*)/);
  }


  function isKeyword(possibleKeyword) {
    return [
      'abstract',
      'boolean', 'break', 'byte',
      'case', 'catch', 'char', 'class', 'const', 'continue',
      'debugger', 'default', 'delete', 'do', 'double',
      'else', 'enum', 'export', 'extends',
      'false', 'final', 'finally', 'float', 'for', 'function',
      'goto',
      'if', 'implements', 'import', 'in', 'instanceof', 'int', 'interface',
      'long',
      'native', 'new', 'null',
      'package', 'private', 'protected', 'public',
      'return',
      'short', 'static', 'super', 'switch', 'synchronized',
      'this', 'throw', 'throws', 'transient', 'true', 'try', 'typeof',
      'var', 'void', 'volatile',
      'while', 'with'].indexOf(possibleKeyword) >= 0;
  }


  function isNumber(text, i) {
    isToken(text, i, /^([0-9][0-9]*\.?[0-9]*([eE][-+]?[0-9]+)?)/);
  }


  function isString(text, i) {
    var c = text.charAt(i);
    if (c != '\'' && c != '"') {
      return -1;
    }

    for (var j = i + 1; j < text.length; ++j) {
      var c2 = text.charAt(j);

      if (c2 == c) {
        return j + 1;
      }

      if (c2 == '\\') {
        ++j;
      }
    }

    return j;
  }


  function isSingleLineComment(text, i) {
    return isToken(text, i, /^(\/\/.*)\n/);
  }


  function isMultiLineComment(text, i) {
    return isToken(text, i, /^(\/\*(?:.|\n)*\*\/)/m);
  }


  //
  // the code
  //

  elem = $(elem);
  var text = elem.textContent;

  var result = $A();
  var lineNumber = 0;
  addLineNumber(result, ++lineNumber);

  var end;
  for (var i = 0; i < text.length;) {

    // ID or keyword
    end = isId(text, i);

    if (end >= 0) {
      var value = text.substring(i, end);
      addResult(result, (isKeyword(value) ? 'jsKey' : 'jsId'), value);
      i = end;

      // number
    } else if ((end = isNumber(text, i)) >= 0) {
      value = text.substring(i, end);
      addResult(result, 'jsNumber', value);
      i = end;

      // string
    } else if ((end = isString(text, i)) >= 0) {
      value = text.substring(i, end);
      addResult(result, 'jsString', value);
      i = end;

      // single line comment
    } else if ((end = isSingleLineComment(text, i)) >= 0) {
      value = text.substring(i, end);
      addResult(result, 'jsCommentS', value);
      i = end;

      // multi line comment
    } else if ((end = isMultiLineComment(text, i)) >= 0) {
      value = text.substring(i, end);
      var count = 0;
      $A(value.split('\n')).each(function(item) {
        addResult(result, 'jsCommentM', item);
        if (++count > 1) {
          addResult(result, 'br', null);
          addLineNumber(result, ++lineNumber);
        }
      });
      i = end;

      // single character
    } else {
      var c = text.substring(i, i + 1);

      if (c == '\n') {
        addResult(result, 'br', null);
        addLineNumber(result, ++lineNumber);
      } else {
        addResult(result, null, c);
      }

      ++i;
    }
  }

  while (elem.hasChildNodes()) {
    elem.removeChild(elem.lastChild);
  }

  result.each(function(item) {
    elem.appendChild(item);
  });
}