/* vim: set ai sts=4 sw=4 : */
/*
 * This is a 'barely good enough' implementation of String.prototype.replace to
 * patch old Internet Explorer and Safari browsers.  The old browsers didn't
 * allow a function to be passed as the second argument to replace.
 *
 * There are problems with most non-native implementations of replace when the
 * first argument is a regular-expression - this can cause infinite loops on
 * zero-width matches:
 * 
 * This implementation (should) avoid infinite loops, but it can display incorrect behaviour in some circumstances:
 * e.g.
 * var i = 0; alert("wrap words".replace(/\b/g, function() { return '[]'.charAt(i++ % 2) }));
 * should alert with "[wrap] [words]", but doesn't...
 */
if (''.replace('', String)) String.prototype.replace = new function() {
  /**
   * Determine if a regular-expression is a global reg-exp or not (required for IE5)
   * @param re an instance-of RegExp
   * @return <code>true</code> iff RegExp uses the 'global' flag.
   */
  function global(re) {
    var s = String(re);
    return re.global || s.indexOf('g', s.lastIndexOf('/')) > 0;
  }

  function replaceFunction(searchValue, replaceValue) {
    // defensive programming: this shouldn't ever happen!
    if (typeof(replaceValue) != 'function') {
      this.replace = stringReplace;
      return this.replace(searchValue, replaceValue);
    }

    var string = String(this);
    if ((!searchValue instanceof RegExp)) {
      var searchString = String(searchValue), offset = string.indexOf(searchString);
      return offset < 0 ? string : string.substring(0, offset) + replaceValue(searchString, offset, string) + string.substring(offset + searchString.length);
    } else {
      var m = string.match(searchValue);
      if (!m) {
	return string;
      } else if (!global(searchValue)) {
	// youngpup's hack:
	var params = []; for (var i = 0; i < m.length; i++) { params[i] = 'm[' + i + ']'; }
	return string.substring(0, m.index) + eval('replaceValue(' + params + ', m.index, string)') + string.substring(m.index + m[0].length);
      } else {
	// now the fun starts...
	var values = [];
	// (Note: there MUST be an easier way to do this...)
	var pattern = searchValue.toString();
	var search = new RegExp(pattern.substring(1, pattern.lastIndexOf('/')), pattern.substring(pattern.lastIndexOf('/')+1).replace(/g/g, ''));
	var k = m.length, gather = '', remainder = string;
	while (k--) {
	  m = remainder.match(search);
	  // Note: there WILL be a match, but we program for worst-case behaviour
	  if (!m) break;
	  // youngpup's hack:
	  var params = []; for (var i = 0; i < m.length; i++) { params[i] = 'm[' + i + ']'; }
	  gather += remainder.substring(0, m.index) + eval('replaceValue(' + params + ', m.index + (string.length - remainder.length), string)');

	// TODO: explore 'unlikely characters' idea, where-by we do:
	// var mock = string.replace(search/g, '\u0000$&');
	// this should return a string with null-characters positioned appropriately for us to calculate offsets for each substitution
	// Note: might need to wrap reg-exp in outer '(' ')' pair as replace(/.../g, '\u0000$&') doesn't work in IE5
	  // TODO: this needs fixing - it's to stop infinite loop on
	  // zero-width matches, but it looks like it causes
	  // characters to be skipped...
	  // remainder = remainder.substring(m.index + (m[0].length || 1));
	  // Experimental:
	  // if m[0].length == 0 we have a zero-width match, but we need to know how much to skip...
	  if (m[0].length == 0) {
	    var toSkip = remainder.substring(m.index).split(search);
	    alert([remainder, m.index, search, toSkip]);
	    gather += toSkip[0];
	    remainder = remainder.substring(m.index + toSkip[0].length);
	  } else {
	    remainder = remainder.substring(m.index + (m[0].length || 1));
	  }
	}
	return gather + remainder;
      }
    }
  }

  var stringReplace = ''.replace;
  return function(searchValue, replaceValue) {
    var generic = this.replace;
    // TODO: try/finally
    this.replace = (typeof replaceValue != 'function') ? stringReplace : replaceFunction;
    var result = this.replace(searchValue, replaceValue);
    this.replace = generic;
    return result;
  }
};

