/** var $S = pulp.string; */
pulp.Modules.string = '$S';

(function() {
 
  var $p = pulp.base;
 
  /**
   * String-manipulation functions
   * 
   * @name pulp.string
   * @namespace
   * @requires base
   */
  var $S = pulp.string = /** @scope pulp.string */{
    /**
     * Find and replace all instances of a given RegExp or string with a string or function
     * 
     * Callback functions receive two parameters: the current array of match+subpatterns and the number of strings found previously
     * 
     * @function
     * @param {String} string  The string in which to search
     * @param {RegExp|String} pattern  The RegExp or string to search for
     * @param {String|Function} replacement  The string or function to use to replace the found string
     * @param {Number} limit [optional]  The maximum number of replacements to allow [default = 0]
     * @return {String}  The string with replacements made
     */
    replaceAll: $p.replaceAll,
    /**
     * Find all instances of a given RegExp or string with a string or function
     * 
     * Callback functions receive two parameters: the current array of match+subpatterns and the number of strings found previously
     * 
     * @param {String} string  The string in which to search
     * @param {RegExp|String} pattern  The RegExp or string to search for
     * @param {Function} callback [optional]  The string or function to use to replace the found string
     * @param {Number} limit [optional]  The maximum number of matches to return [default = 0]
     * @return {Array}  Array of matches found
     */    
    findAll: function(string, pattern, callback, limit) {
      var found = [], source = string, match, count = 0;
      limit = parseFloat(limit);

      while (match = source.match(pattern)) {
        count++;
        found.push(match);
        if (typeof callback == 'function') {
          callback(match, count);
        }
        source = source.slice(match.index + match[0].length);
        if (limit > 0 && limit <= count) {
          break;
        }
      }
      return found;
    },
    /**
     * Truncate a string to the given length
     * 
     * @param {String} string  The string to truncate
     * @param {Number} length [optional]  The maximum string length (including elipses string); [default=30]
     * @param {String} elipses [optional]  The string to indicate the string was truncated; [default="..."]
     * @return {String}
     */
    truncate: function(string, length, elipses) {
      length = length || 30;
      elipses = elipses === undefined ? '...' : elipses;
      return string.length > length ? string.slice(0, length - elipses.length) + elipses : string;
    },
    /**
     * Return a string with no whitespace at the beginning or end
     * 
     * @function
     * @param {Object} string
     * @return {String}
     */
    trim: $p.trim,
    /**
     * Return a string with html tags removed
     * 
     * @function     
     * @param {Object} string
     * @return {String}
     */
    stripTags: $p.stripTags,
    /**
     * Convert an object of unknown type to a string
     * 
     * @function     
     * @param {Object}
     * @return {String}
     */
    interpret: $p.castAsString,
    /**
     * Convert hyphenated strings to camelcase strings
     * e.g. border-left-width => borderLeftWidth
     *      
     * @param {String} string
     * @return {String}
     */
    camelize: function(string) {
      // slower than split for small strings, but properly passes unit tests with stray dashes      return $p.castAsString(string).replace((/-([a-z])/g), function(match) {
        return match[1].toUpperCase();
      });
    },
    /**
     * Make the first character of a string uppercase and the rest lowercase
     * 
     * @param {String} string
     * @return {String}
     */
    capitalize: function(string) {
      string = string.toLowerCase();
      return string.substring(0, 1).toUpperCase() + string.substring(1);
    },
    /**
     * Convert characters to html entities
     * e.g. & => &amp;
     * 
     * @function     
     * @param {String} string
     * @return {String}
     */
    escapeHTML: $p.escapeHTML,
    /**
     * Convert html entities to characters
     * e.g. &amp; => &
     * 
     * @param {String} string
     * @return {String}
     */
    unescapeHTML: function(string) {
      // TODO: ensure this passes Prototype unit tests-- there might be some cross-browser issues
      string = $p.stripTags(string);
      var el = document.createElement("DIV");
      el.innerHTML = string;
      return el.textContent || el.innerText;
    },
    /**
     * Return true if needle is found in haystack
     * 
     * @param {String} haystack
     * @param {String} needle
     * @return {Boolean}
     */
    include: function(haystack, needle) {
      return haystack.indexOf(needle) > -1;
    },    
    /**
     * Return true if needle is found at the beginning haystack
     * 
     * @param {String} haystack
     * @param {String} needle
     * @return {Boolean}
     */
    startsWith: function(haystack, needle) {
      return haystack.indexOf(needle) == 0;
    },
    /**
     * Return true if needle is found at the end of haystack
     * 
     * @param {String} haystack
     * @param {String} needle
     * @return {Boolean}
     */
    endsWith: function(haystack, needle) {
      return haystack.length >= needle.length ? (haystack.lastIndexOf(needle) == haystack.length - needle.length) : false;
    }

  };
  
  $p.extend($S, /** @scope pulp.string */{
    /**
     * alias of {@link pulp.string.replaceAll}
     * @function
     */
    gsub: $S.replaceAll,
    /**
     * alias of {@link pulp.string.findAll}
     * @function
     */
    sub: $S.findAll
  });
    
  var container = document.createElement('pre');
  var text = document.createTextNode('');
  
  container.appendChild(text);
  
  if ($S.unescapeHTML('1\n2') === '1\r2') {
    // IE converts all newlines to carriage returns so we swap them back
    $S.unescapeHTML = $p.wrapFunction($S.unescapeHTML, function(proceed) {
      return proceed().replace(/\r/g, '\n');
    });
  }
  
  if ($S.escapeHTML('>') !== '&gt;') {
    // Safari 3.x has issues with escaping the ">" character
    $S.unescapeHTML = $p.wrapFunction($S.unescapeHTML, function(proceed) {
      return proceed().replace(/>/g, "&gt;");
    });
  }
  
  if ($S.escapeHTML('&') !== '&amp;') {
    // Safari 2.x has issues with escaping html inside a "pre" element so we use the deprecated "xmp" element instead
    container = document.createElement('xmp');
    container.appendChild(text);
  }
  
  /**
   * Export string methods to String.prototype
   * @name pulp.string.exportToPrototype
   */
  $S.exportToPrototype = function() {
    $p.each($S, function(fn, name) {
      if (typeof fn == 'function' && fn != $S.exportToPrototype) {
        String.prototype[name] = $p.methodize(fn);
      }
    })
  };
  
  // TODO: allow $S object creation

})(pulp.base);
