/** 
 * Raine's Javascript Extensions 
 * Commonly used functions that should be built-in, but aren't, and that we don't mind polluting the global namespace with. 
 * Client-side and server-side compatible
 * Last Updated: 10/06/10
 */

/**
String.prototype.format
String.prototype.trim
Function.prototype.Extend
Function.prototype.Super
Function.prototype.Implements
Function.prototype.Context
Function.prototype.compose
Function.prototype.sequence
Function.prototype.curry
Function.prototype.rcurry
map
filter
reduce
range
each
find
contains
pluck
toArray
isArray
isEmpty
compare
compareProperty
equals
assert
assertEquals
*/

/***********************************
 * String overrides
 ***********************************/

/** Replaces occurrences of {0}, {1}, ... with each additional argument passed.  Like sprintf. 
	Caches compiled regular expressions to improve performance.
*/
String.prototype.format = (function() { 
	
	// private vars
	var preRE = [];
	
	return function() {

		var str = this;

		for(var i=0, l=arguments.length; i<l; i++) {
			// cache regular expression
			if(!preRE[i]) {
				preRE[i] = new RegExp();
				preRE[i].compile('\\{' + (i) + '\\}','gm');
			}
			str = str.replace(preRE[i], arguments[i]);
		}

		return str;
	}
})();


/** Removes whitespace from both ends of a string. */
String.prototype.trim = function() {
	return this.replace(/^\s\s*/, '').replace(/\s\s*$/, '');
};


/*************************
 * Inheritance
 *************************/

// Prototypal inheritance comes from wsdom.js
/* Extend from another prototyped Function */
Function.prototype.Extend = function(superClass) {
	this.prototype = new superClass();

	this.prototype.getSuperClass = function() {
		return superClass;
	};
	this.getSuperClass = this.prototype.getSuperClass;
	return this;
};

/*
The Super method allows us to do the constructor (or any super call) chaining more easily.  It looks like this:

var A = function() {
  this.value = ["1"]
};

var B = function() {
  B.Super(this);
  this.value.push("2");
};

Super calls for non constructor methods look like:
Class.Super(this, "methodName", arguments);
*/

Function.prototype.Super = function(context, methodName, args) {
	if (null != methodName) {
		var method = this.getSuperClass().prototype[methodName];
	}
	else {
		var method = this.getSuperClass();
	}

	if (!args) {
		return method.call(context);
	}
	else {
		return method.apply(context, args);
	}
};

/*
Function.Implements
It takes either an instantiated object or a functor, and a list of properties/methods and add
them to the calling functor.  Be careful not to apply methods that call other methods/object in
the passed functor/object that do not exist in the calling functor.
*/
Function.prototype.Implements = function(obj, members) {
	if(typeof obj == "function") {
		obj = obj.prototype;
	}
	var tObj = {}
	for(var i = 0, len = members.length; i < len; ++i) {
		tObj[members[i]] = obj[members[i]] || null;
	}
	var o = WSDOM.Util.copyObject(tObj);
	for(var i in o) {
		this.prototype[i] = o[i];
	}
};


/***********************************
 * Function overrides
 ***********************************/

/**
 * Returns a function that applies the underlying function
 * to the result of the application of `fn`.
 *
 * Based on Functional library by Oliver Steele
 * http://osteele.com/javascripts/functional
 */
Function.prototype.compose = function(fn) {
    var self = this;
    return function() {
        return self.apply(this, [fn.apply(this, arguments)]);
    }
};
/**
 * Returns a function that applies the underlying function
 * to the result of the application of `fn`.
 */
Function.prototype.sequence = function(fn) {
    var self = this;
    return function() {
        return fn.apply(this, [self.apply(this, arguments)]);
    }
};
/**
 * Returns a function that, applied to an argument list $arg2$,
 * applies the underlying function to $args ++ arg2$.
 * :: (a... b... -> c) a... -> (b... -> c)
 * == f.curry(args1...)(args2...) == f(args1..., args2...)
 */
Function.prototype.curry = function(/*args...*/) {
    var fn = this;
    var args = Array.slice(arguments, 0);
    return function() {
        return fn.apply(this, args.concat(Array.slice(arguments, 0)));
    };
};
/*
 * Right curry.  Returns a function that, applied to an argument list $args2$,
 * applies the underlying function to $args2 + args$.
 * == f.curry(args1...)(args2...) == f(args2..., args1...)
 * :: (a... b... -> c) b... -> (a... -> c)
 */
Function.prototype.rcurry = function(/*args...*/) {
    var fn = this;
    var args = Array.slice(arguments, 0);
    return function() {
        return fn.apply(this, Array.slice(arguments, 0).concat(args));
    };
};

/** Calls the function in the scope of the given object. */
Function.prototype.Context = function(obj) {
	var fnReference = this;
	return function () {
		return typeof fnReference == "function" ? fnReference.apply(obj, arguments) : obj[fnReference].apply(obj, arguments);
	};
};


/***********************************
 * Functional
 ***********************************/

/** Returns a new array consisting of f applied to each item of the given array. 
	@param arr		An array of items.
	@param f		(item index) => newValue
*/
map = function(arr, f, context) {

	// error handling
	if(!arr) {
		throw new Error("Array is null or undefined.");
	}
	if(!arr.length && arr.length !== 0) {
		throw new Error("Invalid array (length property doesn't exist): " + arr);
	}
	else if(!f || !f.apply) {
		throw new Error("You must provide a valid function as the first argument to map: " + f);
	}

	var newArray = [];
	for(var i=0, n=arr.length; i<n; i++) {
		newArray[i] = f.apply(context, [arr[i], i]);
	}
	return newArray;
};

/** Returns a new array consisting of a subset of arr for which the given function returns truthy. */
filter = function(arr, f, context) {
	var newArray = [];
	for(var i=0, n=arr.length; i<n; i++) {
		if(f.apply(context, [arr[i], i])) {
			newArray.push(arr[i]);
		}
	}
	return newArray;
};

/**
 * Applies `fn` to `init` and the first element of `sequence`,
 * and then to the result and the second element, and so on.
 * == reduce(f, init, [x0, x1, x2]) == f(f(f(init, x0), x1), x2)
 * :: (a b -> a) a [b] -> a
 * >> reduce('x y -> 2*x+y', 0, [1,0,1,0]) -> 10
 * 	 (from Oliver Steele's Functional Javascript library)
 */
reduce = function(fn, init, sequence, context) {
    //fn = Function.toFunction(fn);
	sequence = [].concat(sequence);
    var len = sequence.length,
        result = init;
    for (var i = 0; i < len; i++)
        result = fn.apply(context, [result, sequence[i]]);
    return result;
};
 
/** Returns a list of integers from min (default: 0) to max (inclusive). */
range = function(min, max) {

	// override for 1 argument
	if(arguments.length == 1) {
		min = 0;
		max = arguments[0];
	}

	var arr = [];
	for(var i=min; i<=max; i++) {
		arr.push(i);
	}
	return arr;
};


/** Applies the given function to each item in the array.  Same as map but doesn't build and return an array. */
each = function(arr, f, context) {
	for(var i=0, n=arr.length; i<n; i++) {
		f.apply(context, [arr[i], i]);
	}
};

/** Returns the first item in arr for which the function f returns true.  Returns null if no matches are found. */
find = function(arr, f, context) {
	for(var i=0, n=arr.length; i<n; i++) {
		if(f.apply(context, [arr[i], i])) {
			return arr[i];
		}
	}

	return null;
};

/** Returns true if the array contains the given item (compared by address, but works by value for strings). */
contains = function(arr, item) {
	for(var i=0, n=arr.length; i<n; i++) {
		if(arr[0] === item) {
			return true;
		}
	}

	return false;
};

/** Returns an array of values of the given property for each item in the array. */
pluck = function(arr, property) {
	return map(function(item) {
		return item[property];
	}, arr);
};


/***********************************
 * Array/Object/Utility
 ***********************************/

/** Returns an array of the object's values. */
toArray = function(obj) {
	var newArray = [];
	for(var property in obj) {
		newArray.push(obj[property]);
	}
	return newArray;
};

/** Returns true if the array contains the given object. */
inArray = function(arr, obj) {
	return find(arr, function(x) { return x === obj; }) !== null;
};

/** Returns true if the object has no properties, like {}. */
isEmpty = function(obj) {
	for(prop in obj) {
		return false;
	}
	return true;
};

/** Compares two items lexigraphically.  Returns 1 if a>b, 0 if a==b, or -1 if a<b. */
compare = function(a,b) {
	if(a > b) {
		return 1;
	}
	else if(a == b) {
		return 0;
	}
	else {//if(a < b) {
		return -1;
	}
};

/** Returns a function that compares the given property of two items. */
compareProperty = function(property) {
	return function(a,b) {
		return compare(a[property], b[property]);
	};
};

/** Returns true if all the items in a are equal to all the items in b, recursively. */
equals = function(a,b) {
	// compare arrays
	if(a instanceof Array) {

		// check if the arrays are the same length
		if(a.length !== b.length) {
			return false;
		}

		// check the equality of each item
		for(var i=0, l=a.length; i<l; i++) {
			if(!b || !b[i] || !equals(a[i], b[i])) {
				return false;
			}
		}
	}
	// compare scalars
	else {
		if(a !== b) {
			return false;
		}
	}

	return true;
};

/***********************************
 * Assertions
 ***********************************/

/** Asserts that a is truthy. */
assert = function(a,b) {
	if(!a) {
		console.error("Assertion failure: {0}".format(a));
	}
};

/** Asserts that a is equal to b, using recursive equality checking for arrays. */
assertEquals = function(a,b) {
	if(!equals(a,b)) {
		console.error("Assertion failure: {0} === {1}".format(a,b));
	}
};

