/*

Core.js
=======

Adds methods to javascript's built-in data types.
        
Array
-----

a.indexOf, a.lastIndexOf, a.forEach, a.filter, a.map, a.some, a.every
    New in JavaScript 1.6, only implemented in Firefox 1.5.
    
Array.forEach, Array.filter, Array.map, Array.some, Array.every
    New in JavaScript 1.6, only implemented in Firefox 1.5.

a.contains, a.copy, a.insertAt, a.insertBefore, a.removeAt, a.remove, a.last
    Not implemented in any browser.
    
a.random


String
------

s.stripLeft, s.stripRight, s.strip, s.words, s.contains, 
s.startsWith, s.endsWith, s.removeEnding, s.removePunctuation
s.lastIndexOf, s.forEach, s.normalise, s.rot13, s.capitalise
  Not implemented in any browser.
    
*/

//  Array


if (!Array.prototype.indexOf) {
	Array.prototype.indexOf = function (obj, fromIndex) {
  	if (fromIndex == null) {
  		fromIndex = 0;
  	} 
  	else if (fromIndex < 0) {
  		fromIndex = Math.max(0, this.length + fromIndex);
  	}
  	for (var i = fromIndex; i < this.length; i++) {
  		if (this[i] === obj)
  			return i;
  	}
  	return -1;
  };
}

if (!Array.prototype.lastIndexOf) {
	Array.prototype.lastIndexOf = function (obj, fromIndex) {
		if (fromIndex == null) {
			fromIndex = this.length - 1;
		} 
		else if (fromIndex < 0) {
			fromIndex = Math.max(0, this.length + fromIndex);
		}
		for (var i = fromIndex; i >= 0; i--) {
			if (this[i] === obj)
				return i;
		}
		return -1;
	};
}

if (!Array.prototype.forEach) {
	Array.prototype.forEach = function (f, obj) {
		var l = this.length;	// must be fixed during loop... see docs
		for (var i = 0; i < l; i++) {
		    f.call(obj, this[i], i, this);
		}
	};
}

if (!Array.prototype.filter) {
	Array.prototype.filter = function (f, obj) {
		var l = this.length;	// must be fixed during loop... see docs
		var res = [];
		for (var i = 0; i < l; i++) {
		    if (f.call(obj, this[i], i, this)) {
                res.push(this[i]);
            }
		}
		return res;
	};
}

if (!Array.prototype.map) {
	Array.prototype.map = function (f, obj) {
		var l = this.length;	// must be fixed during loop... see docs
		var res = [];
		for (var i = 0; i < l; i++) {
			res.push(f.call(obj, this[i], i, this));
		}
		return res;
	};
}

if (!Array.prototype.some) {
	Array.prototype.some = function (f, obj) {
  	var l = this.length;	// must be fixed during loop... see docs
  	for (var i = 0; i < l; i++) {
  		if (f.call(obj, this[i], i, this)) {
  			return true;
  		}
  	}
    return false;
	};
}

if (!Array.prototype.every) {
	Array.prototype.every = function (f, obj) {
		var l = this.length;	// must be fixed during loop... see docs
		for (var i = 0; i < l; i++) {
			if (!f.call(obj, this[i], i, this)) {
				return false;
			}
		}
		return true;
	};
}

if (!Array.forEach) {
    Array.forEach = function(a, f, obj) {
        Array.prototype.forEach.call(a, f, obj);
    }
}


if (!Array.filter) {
	Array.filter = function (a, f, obj) {
        return Array.prototype.filter.call(a, f, obj);
	};
}

if (!Array.map) {
	Array.map = function (a, f, obj) {
    return Array.prototype.map.call(a, f, obj);
	};
}

if (!Array.some) {
	Array.some = function (a, f, obj) {
    return Array.prototype.some.call(a, f, obj);
	};
}

if (!Array.every) {
	Array.every = function (a, f, obj) {
        return Array.prototype.every.call(a, f, obj);
	};
}

Array.prototype.contains = function (obj) {
  if (obj.constructor == Array) {
    return obj.every(this.contains, this);
  }
	return this.indexOf(obj) != -1;
}

Array.prototype.copy = function (obj) {
	return this.concat();
}

Array.prototype.insertAt = function (obj, i) {
	this.splice(i, 0, obj);
}

Array.prototype.insertBefore = function (obj, obj2) {
	var i = this.indexOf(obj2);
	if (i == -1)
		this.push(obj);
	else
		this.splice(i, 0, obj);
}

Array.prototype.removeAt = function (i) {
	this.splice(i, 1);
}

Array.prototype.remove = function (obj) {
	var i = this.indexOf(obj);
	if (i != -1)
		this.splice(i, 1);
}

Array.prototype.last = function() {
    return this[this.length - 1];
}

Array.prototype.random = function() {
    /*
        a.random()
            Returns a randomly chosen element of a.
            
        a.random(n)
            Returns an array of n randomly chosen elements of a.
    */
    var rv;
    if (arguments.length == 0) {
        rv = this[Math.floor(Math.random() * this.length)];
    }
    else {
        rv = [];
        while (rv.length < arguments[0]) {
            var i = this.random();
            if (rv.contains(i)) continue;
            rv.push(i);
        }
    }
    return rv;
}

//  String

if (!String.prototype.lastIndexOf) {
    String.prototype.lastIndexOf =  function(c) {
        var i = this.length + 1;
        var rv = -1;
        if (arguments[1] != null) {
           i = arguments[1] + 1;
        }
        while (--i >= 0) {
            if (this.substr(i, 1) == c) {
                rv = --i;
                break;
            }
        }
        return rv;
    }
}

String.prototype.stripLeft = function() {
    return this.replace(/^\s+/, '');
}

String.prototype.stripRight = function() {
    return this.replace(/\s+$/, '');
}

String.prototype.strip = function() {
    return this.stripLeft().stripRight();
}

String.prototype.words = function() {
    /*
        s.words()
            Returns an array of the words in the string.
            
        s.words(n)
            Returns the n-th word of the string.
    */
    var rv = this.strip().split(/\s+/);
    if (arguments.length != 0) {
        rv = rv[arguments[0]];
    }
    return rv;
}

String.prototype.contains = function(s) {
  if (s.constructor == Array){
    return s.every(this.contains, this);
  }
  return (this.indexOf(s) != -1);
}

String.prototype.startsWith = function(s) {
    return (this.indexOf(s) == 0);
}

String.prototype.endsWith = function(s) {
    return (this.lastIndexOf(s) != -1 && this.lastIndexOf(s) + s.length == this.length);
}

String.prototype.removeEnding = function(x) {
    /*
        s.removeEnding('string')
           Removes a string from the end of s.
           
        s.removeEnding(<number>)
            Removes <number> characters from the end of s.
    */
    if (typeof x == 'number') {
        return this.substr(0, this.length - x);
    }
    else if (this.endsWith(x)) {
        return this.substr(0, this.length - x.length);
    }
    else {
        return this;
    }
}

String.prototype.removePunctuation = function() {
  return this.replace(/[.,;:?]/g, ' ')
}

String.prototype.forEach = function (f, obj) {
	var l = this.length;	// must be fixed during loop... see docs
	for (var i = 0; i < l; i++) {
	    f.call(obj, this.charAt(i), i, this);
	}
};

String.prototype.normalise = function() {
  return this.removePunctuation().strip().toLowerCase();
}

String.prototype.rot13 = function() {
  var alphabet = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ";
  var convertedAlphabet = "nopqrstuvwxyzabcdefghijklmNOPQRSTUVWXYZABCDEFGHIJKLM";
  var rv = "";
  this.forEach(function(chr) {
    if (alphabet.contains(chr)) {
      rv += convertedAlphabet.charAt(alphabet.indexOf(chr));
    }
    else {
      rv += chr;
    }
  });
  return rv;
}

String.prototype.capitalise = function() {
  return this.replace(/\w+/g, function(a) {
    return a.charAt(0).toUpperCase() + a.substr(1).toLowerCase();
  });
};