text: ﻿"use strict";
  
  var Q = require('q');
  
  module.exports = {
  	/**
  	 * Checks whether a URL string starts with a scheme (or is protocol-relative).
  	 */
  	hasScheme: function (uri) {
  		return (/^([a-z][a-z.+-]+:|\/\/)/i).test(uri);
  	},
  
  	/**
  	 * Copies and modifies an array, returning the original array if nothing was modified.
  	 * @param {Array}		arr			The original array to loop through.  Neither the array nor its elements will be changed.
  	 * @param {Function}	callback	A function that may modify elements.  The function is passed the original item and its index.
  	 *									Returning undefined will cause the item to remain unchanged; returning null will remove the item entirely.
  	 *									Returning an array will insert the items in the array instead of the original element.
  	 *									The function can also return a promise.
  	 * @return  An array, if the callback never returned a promise, or a promise of an array if it did.
  	 */
  	modifyArrayCopy: function (arr, callback) {
  		if (!arr.length) return arr;
  
  		var newArray = null;
  		var index = -1;
  
  		function traverse(newVal) {
  			while (true) {
  				// Unless we just got started, process the result of
  				// the previous iteration, or the promise we resumed
  				// traverse() from.
  				if (index >= 0) {
  					if (typeof newVal === "undefined" || newVal === arr[index]) {
  						// If the item didn't change, but we had an earlier
  						// change, push the original item to the new array.
  						newArray && newArray.push(arr[index]);
  					} else {
  						// If the item was changed, and this is the first change,
  						// create the new array, filling it with all of the prior
  						// items
  						if (!newArray)
  							newArray = arr.slice(0, index);
  
  						// If the item wasn't removed, push the new value on
  						// to the new array.
  						if (newVal instanceof Array)
  							newArray.push.apply(newArray, newVal);
  						else if (newVal !== null)
  							newArray.push(newVal);
  					}
  				}
  				index++;
  
  				// Once we finish the entire array, return
  				// the final result to the caller.
  				if (index === arr.length)
  					return newArray || arr;
  
  				newVal = callback(arr[index], index);
  				// If the next transformation is a promise,
  				// break out of this call, then resume when
  				// the result comes back.
  				if (Q.isPromiseAlike(newVal))
  					return Q.when(newVal, traverse);
  				// Otherwise, process the actual value next
  				// time around the loop
  			}
  		}
  		return traverse();
  	},
  	/**
  	 * Modifies an array in-place.
  	 * @param {Array}		arr			The original array to loop through.
  	 * @param {Function}	callback	A function that may modify elements.  The function is passed the original item and its index.
  	 *									Returning undefined will cause the item to remain unchanged; returning null will remove the item entirely.
  	 *									Returning an array will insert the items in the array instead of the original element.
  	 *									The function can also return a promise.
  	 */
  	modifyArray: function (arr, callback) {
  		if (!arr.length) return Q.resolve(arr);
  		var promise = arr;
  
  		var indexOffset = 0;
  		arr.forEach(function (oldVal, i) {
  			promise = Q.spread(
  				[promise, callback(oldVal, i)],
  				// Wait for both all previous operations and the result of this callback
  				// I need to wait for all previous operations in case removals happen in
  				// the wrong order.  (I need to use the original index)
  				function (_, newVal) {
  					if (typeof newVal === "undefined")
  						newVal = oldVal;
  
  					if (newVal instanceof Array) {
  						newVal.unshift(i + indexOffset, 1);
  						arr.splice.apply(arr, newVal);
  						// All future indices must be incremented because of the extra items.
  						// newVal.length includes the two parameters to splice.
  						indexOffset += newVal.length - 3;
  					} else if (newVal !== null)
  						arr[i + indexOffset] = newVal;
  					else {
  						arr.splice(i + indexOffset, 1);
  						indexOffset--;	//All future indices must be decremented to fill the gap.
  					}
  
  					// Make sure the final promise returns the correct array.
  					return arr;
  				}
  			);
  		});
  
  		return promise;
  	},
  
  	/**
  	 * Modifies an array in-place, synchronously.
  	 * @param {Array}		arr			The original array to loop through.
  	 * @param {Function}	callback	A function that may modify elements.  The function is passed the original item and its index.
  	 *									Returning undefined will cause the item to remain unchanged; returning null will remove the item entirely.
  	 *									Returning an array will insert the items in the array instead of the original element.
  	 *									Promises are not accepted.
  	 */
  	modifyArraySync: function (arr, callback) {
  		var indexOffset = 0;
  		for (var i = 0; i < arr.length; i++) {
  			//Pass the original index to the callback
  			var newVal = callback(arr[i], i - indexOffset);
  			if (typeof newVal === "undefined")
  				continue;
  
  			if (newVal instanceof Array) {
  				newVal.unshift(i, 1);
  				arr.splice.apply(arr, newVal);
  				// All future indices must be incremented because of the extra items.
  				// newVal.length includes the two parameters to splice.
  				indexOffset += newVal.length - 3;
  				i += newVal.length - 3;
  			} else if (newVal !== null) {
  				arr[i] = newVal;
  			} else {
  				arr.splice(i + indexOffset, 1);
  				indexOffset--;	//All future indices must be decremented to fill the gap.
  				i--;
  			}
  		}
  		return arr;
  	},
  
  	/**
  	 * A URL transform function that does not modify URLs.
  	 * This is used as a default if no other options are specified.
  	 * The function is shared with other files so that the preprocessor 
  	 * can skip crawling the HTML if no URL option was passed.
  	 */
  	noopUrlTransform: function (url) { return url; }
  };