var un = undefined, undef = undefined;

var jscore = {

	/**
	 * Returns a function with a bound 'this' object reference and optionally
	 * some parameter references to prefix those passed later.
	 *
	 * otherObject.bound = bound = jscore.bind (object, 'func', a);
	 * // the following are now equivalent:
	 * object.func (a, b);
	 * bound (b);
	 */
	bind: function (object, func) {
		if (arguments.length > 2) {
			var args = jscore.argsSlice (arguments, 2);
			if (typeof (func) == 'string') {
				return function () {
					return object [func].apply (object, jscore.join (args, jscore.argsSlice (arguments)));
				};
			} else {
				return function () {
					return func.apply (object, jscore.join (args, jscore.argsSlice (arguments)));
				};
			}
		} else {
			if (typeof (func) == 'string') {
				return function () {
					return object [func].apply (object, arguments);
				};
			} else {
				return function () {
					return func.apply (object, arguments);
				};
			}
		}
	},

	/**
	 * Returns the number of bytes in the passed string's utf-8 representation.
	 */
	utfLen: function (data) {
		var ret = 0;
		for (var i = 0; i < data.length; i++) {
			var c = data.charCodeAt (i);
			if (c < 0x80) ret += 1;
			else if (c < 0x800) ret += 2;
			else if (c < 0x10000) ret += 3;
			else if (c < 0x200000) ret += 4;
			else if (c < 0x4000000) ret += 5;
			else ret += 6;
		}
		return ret;
	},

	/**
	 * Returns a nicely formatted string for a caught error.
	 */
	errorSummary: function (error) {
		switch (typeof error) {
		case 'string':
			return error;
		case 'object':
			if ('fileName' in error)
				return error.fileName + ':' + error.lineNumber + ' ' + 
					error.message + ' (' + error.name + ')';
			else return error.message;
		default:
			return jscore.pretty (error);
		}
	},

	/**
	 * Takes an integer and returns a string. If length is less than 2 then returns with a 
	 * leading zero, useful for dates etc.
	 */	
	leadingZero: function (value) {
		return String(value).length < 2 ? '0'+value : String(value);
	},
	/**
	 * Removes anything but numeric characters, eg: 400px becomes 400 
	 * leading zero, useful for dates etc.
	 */	
	returnNumeric: function (input) {
		return input.replace(/[^0-9]+/g,'');
	},

	min: function () {
		var value = undefined;
		for (var i = 0; i < arguments.length; i++)
			if (value == undefined || value > arguments [i])
				value = arguments [i];
		return value;
	},

	max: function () {
		var value = undefined;
		for (var i = 0; i < arguments.length; i++)
			if (value == undefined || value < arguments [i])
				value = arguments [i];
		return value;
	},

	/**
	 * Easy prototype class definition. Takes a constructor name, list of superclasses and an
	 * object to import prototype members from.
	 *
	 * The returned function will a newly created prototype, into which are imported the members
	 * from all superclasses and from the object supplied. When called it delegates to the named
	 * constructor. This makes it easy to chain constructors as you can simply call the
	 * superclasses' constructors by name.
	 *
	 * As this copies prototype members rather than chaining them, items later added to the
	 * superclass prototypes don't automatically become available to us. But you probably didn't
	 * want to do that anyway.
	 *
	 * var DerivedClass = jscore.def ('DerivedClass', SuperClass0, SuperClass1, {
	 *   DerivedClass: function (a, b, c) {
	 *     this.SuperClass0 (a);
	 *     this.SuperClass1 (b);
	 *     this.c = c;
	 *   },
	 *   someFunc: function () { ... }
	 * };
	 */
	def: function (/* ... */) {
		var name = arguments [0];
		var props = arguments [arguments.length - 1];

		// check it looks alright
		if (! (name in props))
			throw 'Constructor not found: ' + name;

		// create the class object
		var classObj = {
			name: name,
			props: props
		};

		// Create the real constructor/class.
		var ret = function () {
			this [name].apply (this, arguments);
		};
		ret.name = name;

		// import system stuff
		ret.prototype.__class = classObj;

		// import our properties
		jscore.mergeIntoNew (ret.prototype, props);

		// import superclass properties
		for (var i = arguments.length - 2; i >= 1; i--)
			jscore.mergeIntoNew (ret.prototype, arguments [i].prototype);

		// and return
		return ret;
	},

	/*defMerge: function (dest, src, name) {
		for (var key in src) {
			if (! (src in dest)) {
				dest [key] = src [key];
				if (key != name)
					dest [name + '_' + key] = src [key];
			}
		}
	},*/

	merge: function (/* ... */) {
		var dest = {};
		for (var i = 0; i < arguments.length; i++) {
			jscore.mergeInto (dest, arguments [i]);
		}
		return dest;
	},

	/**
	 * Merges a set of objects' properties into the destination object.
	 *
	 * mergeInto (dest, src0, src1, ...);
	 */
	mergeInto: function (/* ... */) {
		var dest = arguments [0];
		for (var i = 1; i < arguments.length; i++) {
			var src = arguments [i];
			for (var key in src)
				dest [key] = src [key];
		}
		return dest;
	},

	/**
	 * Merges a set of objects' properties into the destination object.
	 *
	 * mergeInto (dest, [ src0, src1, ... ]);
	 */
	mergeIntoReal: function (dest, srcs) {
		for (var i = 0; i < srcs.length; i++) {
			var src = srcs [i];
			for (var key in src)
				dest [key] = src [key];
		}
	},

	/**
	 * Merges a set of objects' properties into the destination object, ignoring those that already
	 * exist.
	 *
	 * mergeIntoNew (dest, src0, src1, ...);
	 */
	mergeIntoNew: function (/* ... */) {
		var dest = arguments [0];
		for (var i = 1; i < arguments.length; i++) {
			var src = arguments [i];
			for (var key in src) {
				if (! (key in dest))
					dest [key] = src [key];
			}
		}
	},

	/**
	 * Merges a set of objects' properties into the destination object, ignoring those that already
	 * exist.
	 *
	 * mergeIntoNewReal (dest, [src0, src1, ...]);
	 */
	mergeIntoNewReal: function (dest, srcs) {
		for (var i = 0; i < srcs.length; i++) {
			var src = srcs [i];
			for (var key in src) {
				if (! (key in dest))
					dest [key] = src [key];
			}
		}
	},

	/**
	 * Creates a debug-friendly function call log message from its name and argument list.
	 */
	callSummary: function (name, args) {
		var s = name + ' (';
		if (jscore.isArray (args) || jscore.isArguments (args)) {
			var first = true;
			for (var i = 0; i < args.length; i++) {
				if (! first) s += ', ';
				s += jscore.pretty (args [i]);
				first = false;
			}
		} else if (jscore.isMap (args)) {
			var first = true;
			for (var i in args) {
				if (! first) s += ', ';
				s += i + ': ' + jscore.pretty (args [i]);
				first = false;
			}
		} else if (args == undefined) {
			s += 'undefined';
		} else {
			s += '?' + jscore.pretty (args) + '?';
		}
		return s + ')';
	},

	/**
	 * Makes the specified named function call by the specified handler, which can be either a
	 * handler function (taking the name and argument list) or a handler object, with the functions
	 * as its properties.
	 */
	handle: function (handlerAny, funcName, args) {
		switch (typeof (handler)) {

		case 'function':
			return handlerAny (funcName, args);

		case 'object':
			return handerAny [funcName].apply (handlerAny, jscore.isArray (args)? args : [ args ]);

		default:
			throw 'Eek';
		}
	},

	handlerFunc: function (handlerAny, errorIfNotFound, prefix) {

		if (jscore.isUndefined (errorIfNotFound))
			errorIfNotFound = true;

		if (jscore.isUndefined (prefix))
			prefix = '';

		switch (typeof (handlerAny)) {

		case 'function':
			return handlerAny;

		case 'object':
			var rest = jscore.argsSlice (arguments, 3);
			return function (name, args) {

				// do before if specified
				if (prefix + 'before' in handlerAny) {
					handlerAny [prefix + 'before'].apply (handlerAny, jscore.join (rest, [ name, args ]));
				}

				var retVal;
				if (prefix + name in handlerAny) {

					// call the named function
					if (jscore.isArray (args)) {
						retVal = handlerAny [prefix + name].apply (handlerAny, args);
					} else if (jscore.isMap (args)) {
						var fn = handlerAny [prefix + name];
						if (fn.length == 0) {
							retVal = fn.apply (handlerAny, jscore.join (rest, [ ]));
						} else if (fn.length == 1) {
							retVal = fn.apply (handlerAny, jscore.join (rest, [ args ]));
						} else if (fn.length == 2) {
							retVal = fn.apply (handlerAny, jscore.join (rest, [ name, args ]));
						}
					} else {
						retVal = handlerAny [prefix + name].apply (handlerAny, rest);
					}

				} else if (prefix + 'any' in handlerAny) {

					// call the any function
					return handlerAny [prefix + 'any'].apply (handlerAny, jscore.join (rest, [ name, args ]));

				} else if (errorIfNotFound) {

					// throw an error
					throw 'No handler for ' + name;
				}

				// do after if specified
				if (prefix + 'after' in handlerAny) {
					handlerAny [prefix + 'after'].apply (handlerAny, jscore.join (rest, [ name, args ]));
				}

				return retVal;
			};

		case 'undefined':
			return function (name, args) {
			};

		default:
			throw 'Eek ' + typeof (handlerAny);
		}
	},
	
	/**
	 * Creates a handler function which finds the named function in one object but calls it with
	 * "this" set to another. This allows a set of handler functions to be grouped and defined in
	 * an object's prototype while still having access to its instance members.
	 *
	 * For example:
	 *
	 *   jscore.def ('SomeClass', {
	 *     SomeObject: function (a) {
	 *       this.a = a;
	 *     },
	 *     handlers: {
	 *       someHandler: function () { return this.a; }
	 *     }
	 *   });
	 *   var inst = new SomeClass (123);
	 *   funcWantingHandler (jscore.handlerFuncSpecial (inst, inst.handlers));
	 *
	 * The handler function returned will delegate a call for "someHandler" to the same named
	 * function, but the reference to "this" in that function will be to the instance specified, as
	 * you might intuitively expect.
	 */
	handlerFuncSpecial: function (mainObject, childObject) {
		return function (name, args) {
			if (! (name in childObject))
				throw 'No handler for ' + name;
			return childObject [name].apply (mainObject, args);
		};
	},

	/**
	 * Performs a "deep copy" of simple data structures.
	 */
	copy: function (data) {

		if (data == undefined) {
			return undefined;
		}

		if (typeof data == 'null') {
			return data;
		}

		if (typeof data == 'number') {
			return data;
		}

		if (typeof data == 'string') {
			return data;
		}

		if (jscore.isArray (data)) {
			var ret = [];
			for (var i in data)
				ret.push (jscore.copy (data [i]));
			return ret;
		}

		if (jscore.isMap (data)) {
			var ret = {};
			for (var i in data)
				ret [i] = data [i];
			return ret;
		}

		if (typeof data == 'boolean') {
			return data;
		}

		throw 'Trying to copy ' + data;
	},

	eq: function () {

		if (arguments.length < 1)
			throw new Exception ('Not enough arguments for jscore.eq');

		for (var i = 1; i < arguments.length; i++) {
			if (! jscore.eqReal (arguments [0], arguments [i])) {
				return false;
			}
		}

		return true;
	},

	eqReal: function (left, right) {

		if (left == null)
			return right == null;

		if (typeof left == 'undefined')
			return typeof right == 'undefined';

		if (typeof left == 'number')
			return typeof right == 'number' && left == right;

		if (typeof left == 'boolean')
			return typeof right == 'boolean' && left == right;

		if (typeof left == 'string')
			return typeof right == 'string' && left == right;

		if (typeof left == 'object' && left instanceof Array)
			return typeof right == 'object' && right instanceof Array && jscore.eqArray (left, right);

		if (jscore.isMap (left))
			return jscore.isMap (right) && jscore.eqMap (left, right);

		throw 'Don\'t know how to handle ' + typeof left;
	},

	eqMap: function (left, right) {

		// check all members from the left
		for (var i in left) {
			if (! (i in right))
				return false;
			if (! jscore.eqReal (left [i], right [i]))
				return false;
		}

		// check there's nothing extra on the right
		for (var i in right) {
			if (! (i in left))
				return false;
		}

		// if we get here we are away
		return true;
	},

	eqArray: function (left, right) {

		if (left.length != right.length)
			return false;

		for (var i in left) {
			if (! jscore.eqReal (left [i], right [i]))
				return false;
		}
		
		return true;
	},

	propNameRegexp: /^[a-zA-z_][a-zA-Z0-9_]*$/,

	/**
	 * Creates a relatively human-friendly JSON representation from a data structure.
	 */
	pretty: function (data) {

		if (data == undefined) {
			return 'undefined';
		}

		if (typeof data == 'number') {
			return '' + data;
		}

		if (typeof data == 'string') {
			return '\'' + data
				.replace (/\u005c/g, '\\\\')
				.replace (/\u0027/g, '\\\'')
				.replace (/\u0000/g, '\\u0000')
				.replace (/\u0008/g, '\\b')
				.replace (/\u0009/g, '\\t')
				.replace (/\u000a/g, '\\n')
				.replace (/\u000b/g, '\\v')
				.replace (/\u000c/g, '\\f')
				.replace (/\u000d/g, '\\r')
				+ '\'';
		}

		if (typeof data == 'array' || data.constructor == Array) {
			s = '[ ';
			var first = true;
			for (var i = 0; i < data.length; i++) {
				if (! first) s += ', ';
				s += jscore.pretty (data [i]);
				first = false;
			}
			return s + ' ]';
		}

		if (typeof data == 'object') {
			var s = '{ ';
			var first = true;
			for (var key in data) {
				if (! first) s += ', ';
				s += (jscore.propNameRegexp.test (key)? key : jscore.pretty (key))
					+ ': ' + jscore.pretty (data [key]);
				first = false;
			}
			return s + ' }';
		}

		if (typeof data == 'boolean') {
			return data? 'true' : 'false';
		}

		if (typeof data == 'function') {
			return 'function(...){...}';
		}

		throw 'Trying to encode ' + data;
	},

	inx: function (left /*, ... */) {
		for (var i = 1; i < arguments.length; i++) {
			if (left == arguments [i])
				return true;
		}
		return false;
	},

	arrayToSet: function (array) {
		var ret = {};
		for (var i = 0; i < array.length; i++)
			ret [array [i]] = true;
		return ret;
	},

	setToArray: function (set) {
		var ret = [];
		for (var key in set)
			ret.push (key);
		return ret;
	},
	objectToArray: function (obj) {
		var ret = [];
		for (var key in obj)
			ret.push (obj[key]);
		return ret;
	},

	map: function () {
		var ret = {};
		for (var i = 0; i < arguments.length; i += 2) {
			ret [arguments [i]] = arguments [i + 1];
		}
		return ret;
	},

	mapKeys: function (map) {
		var ret = [];
		for (var key in map)
			ret.push (key);
		return ret;
	},

	mapNumberKeys: function (map) {
		var ret = [];
		for (var key in map)
			ret.push (Number (key));
		return ret;
	},

	numberSetToArray: function (set) {
		var ret = [];
		for (var i in set)
			ret.push (Number (i));
		return ret;
	},

	stringSetToArray: function (set) {
		var ret = [];
		for (var i in set)
			ret.push (String (i));
		return ret;
	},

	mapValues: function (map) {
		var ret = [];
		for (var i in map)
			ret.push (map [i]);
		return ret;
	},
	
	randomArray: function (in_array, in_len) {
		var ret = [];
		var present = [];
		for (var i= 0; i<in_len; i++){
			var random = Math.floor (in_array.length * Math.random ());
			if(!this.inArray(random,present)){
				present.push(random);
				ret.push (in_array[random]);
			}
		}
		return ret;
	},
	
	ArrayAminusB: function (in_a, in_b) {
	var ret = [];
	var newValue = false;
	for(var i = 0 ; i<in_a.length; i++){
		if((!this.inArray(in_a[i],in_b))){
			ret.push(in_a[i]);
		}
	}
	return ret;
	},
	
	isArray: function (any) {
		if (typeof any != 'object') return false;
		if (! (any instanceof Array)) return false;
		return true;
	},

	isMap: function (object) {
		if (typeof object != 'object') return false;
		if (object == null) return false;
		if (object.constructor != Object && object instanceof object.constructor) return false;
		return true;
	},

	isRegExp: function (any) {
		if (typeof any != 'function') return false;
		if (any.constructor != RegExp) return false;
		return true;
	},

	isString: function (any) {
		return typeof any == 'string';
	},

	isInt: function (any) {
		return typeof any == 'number' && Math.floor (any) == any;
	},

	isIntArray: function (any) {
		if (! jscore.isArray (any)) return false;
		for (var i = 0; i < any.length; i++) {
			if (! jscore.isInt (any [i])) return false;
		}
		return true;
	},

	isStringArray: function (any) {
		if (! jscore.isArray (any)) return false;
		for (var i = 0; i < any.length; i++) {
			if (! jscore.isString (any [i])) return false;
		}
		return true;
	},

	flatten: function (src) {
		var dst = [];
		for (var i = 0; i < arguments.length; i++)
			jscore.flattenReal (dst, arguments [i]);
		return dst;
	},

	flattenReal: function (dst, src) {
		if (jscore.isArray (src)) {
			for (var i = 0; i < src.length; i++) {
				jscore.flattenReal (dst, src [i]);
			}
		} else {
			dst.push (src);
		}
	},

	alert: function () {
		switch (arguments.length) {
		case 1:
			return alert (jscore.pretty (arguments [0]));
		case 2:
			return alert (jscore.callSummary (arguments [0], arguments [1]));
		default:
			throw 'Invalid number of args for jscore.alery: ' + arguments.length;
		}
	},

	isObject: function (any) {
		return typeof any == 'object';
	},

	isBoolean: function (any) {
		return typeof any == 'boolean';
	},

	isFunction: function (any) {
		return typeof any == 'function';
	},

	prettyArgs: function (args) {
		var s = '(';
		for (var i = 0; i < args.length; i++) {
			if (i > 0) s += ', ';
			s += jscore.pretty (args [i]);
		}
		return s + ')';
	},

	argsSlice: function (args, offset, length) {
		if (offset == undefined) offset = 0;
		if (length == undefined) length = args.length - offset;
		if (length < 0) length = 0;
		var ret = Array (length);
		for (var i = offset, j = 0; i < offset + length; i++, j++) {
			ret [j] = i < args.length? args [i] : undefined;
		}
		return ret;
	},

	isUndefined: function (any) {
		return any === undefined;
	},

	isNull: function (any) {
		return any === null;
	},

	createNamedElement: function( type, name ) {
		var element;
		try {
			element = document.createElement('<'+type+' name="'+name+'">');
		} catch (e) { }
		if (!element || !element.name) { // Not in IE, then
			element = document.createElement(type);
			element.name = name;
		}
		return element;
	},

	inArray: function (search, array) {
		for (var i = 0; i < array.length; i++) {
			if (array [i] === search) return true;
		}
		return false;
	},

	randomChoice: function (array) {
		return array [Math.floor (array.length * Math.random ())];
	},

	anyInArray: function (searches, array) {
		for (var i = 0; i < searches.length; i++) {
			for (var j in array)
				if (array[j] == searches[i]) return true;
		}
		return false;
	},

	join: function () {
		var ret = [];
		return ret.concat.apply (ret, jscore.argsSlice (arguments));
	},

	joinInto: function (arr) {
		for (var i = 1; i < arguments.length; i++) {
			for (var j = 0; j < arguments [i].length; j++) {
				arr.push (arguments [i] [j]);
			}
		}
		return arr;
	},

	coalesce: function () {
		for (var i = 0; i < arguments.length; i++) {
			if (arguments [i] != null && arguments [i] != undefined)
				return arguments [i];
		}
		return undefined;
	},

	arrayDel: function (array, elem) {
		var i = array.indexOf (elem);
		if (i >= 0) {
			array.splice (i, 1);
		}
	},

	firstToUpper: function (str) {
		if (str.length == 0) return '';
		return str.substr (0, 1).toUpperCase () + str.substr (1);
	},

	pluralize: function (num, singular, plural) {
		if (! plural) plural = singular + 's';
		if (num == 1) return '1 ' + singular;
		return String (num) + ' ' + plural;
	},

	toYmd: function (date) {
		return '' +
			jscore.zeroPad (date.getFullYear (), 4) + '-' +
			jscore.zeroPad (date.getMonth () + 1, 2) + '-' +
			jscore.zeroPad (date.getDate (), 2);
	},

	zeroPad: function (num, digits) {
		var s = String (num);
		while (s.length < digits)
			s = '0' + s;
		return s;
	},

	numberFormat: function (num, before, after) {
		var prefix = jscore.zeroPad (Math.floor (num), before);
		num = Math.abs (num);
		num = num - Math.floor (num);
		for (var i = 0; i < after; i++) num = num * 10;
		num = Math.round (num);
		var suffix = String (num)
		while (suffix.length < after) suffix = '0' + suffix;
		return prefix + '.' + suffix;
	},

	mapHas: function (map, name) {
		return Object.prototype.propertyIsEnumerable.apply (map, [ name ]);
	},

	mapCount: function (map) {
		var ret = 0;
		for (var key in map) ret++;
		return ret;
	},

	isArguments: function (any) {
		if (typeof any != 'object') return false;
		if (jscore.mapCount (any) != 0) return false;
		if (! Object.prototype.hasOwnProperty.apply (any, [ 'length' ])) return false;
		return true;
	},

	mapEmpty: function (map) {
		for (var key in map) { return false; }
		return true;
	},

	/**
	 * Returns an array with all unique elements from each array passed as
	 * arguments.
	 */
	arrayMerge: function () {
		var set = {}, ret = [];
		for (var i = 0; i < arguments.length; i++) {
			for (var j = 0; j < arguments [i].length; j++) {
				var val = arguments [i] [j];
				if (set [val]) continue;
				set [val] = true;
				ret.push (val);
			}
		}
		return ret;
	},
	
	arrayMergeInto: function (dst) {

		// initialise set with all elements in dst
		var set = {};
		for (var j = 0; j < dst.length; j++) {
			set [dst [j]] = true;
		}

		// then add any from the other args, marking them in set as we go along
		for (var i = 1; i < arguments.length; i++) {
			for (var j = 0; j < arguments [i].length; j++) {
				var val = arguments [i] [j];
				if (set [val]) continue;
				set [val] = true;
				dst.push (val);
			}
		}

		// and return
		return dst;
	},
	
	arrayDiff: function (plus) {
		var set = {}, ret = [];
		for (var i = 1; i < arguments.length; i++) {
			for (var j = 0; j < arguments [i].length; j++) {
				set [arguments [i] [j]] = true;
			}
		}
		for (var i = 0; i < plus.length; i++) {
			if (! set [plus [i]])
				ret.push (plus [i]);
			set [plus [i]] = true;
		}
		return ret;
	},

	arrayUniq: function (arr) {
		var set = {}, ret = [];
		for (var i = 0; i < arr.length; i++) {
			if (set [arr [i]]) continue;
			set [arr [i]] = true;
			ret.push (arr [i]);
		}
		return ret;
	},

	formatCountry: function(country, region, city){
		var locationString = '';
		locationString = locationString + country;
		if(region){
			locationString = locationString + ', '+ region;
		}
		if(city){
			locationString = locationString + ', '+ city;
		}
		return locationString;
	},

	formatDate: function (dob) {
		splitDob = dob.split('-');
		var dob = new Date(splitDob[0], splitDob[1]-1, splitDob[2]);
		var today = new Date();
		var year = today.getFullYear();
		var roughAge = year - splitDob[0];
		today.setFullYear(splitDob[0]);
		if ((today-dob) < 0) roughAge -= 1;
		return roughAge;
	},

	ifUndef: function () {
		for (var i = 0; i < arguments.length; i++) {
			if (arguments [i] == undefined) continue;
			return arguments [i];
		}
		return undefined;
	},
	
	arrayMap: function (arr, fn) {
		var ret = new Array (arr.length);
		for (var i = 0; i < arr.length; i++) {
			ret [i] = fn (arr [i]);
		}
		return ret;
	},

	spliceArray: function (array, index, count, data) {
		var temp = array.slice (index + count);
		array.splice (index);
		array.concat (data, temp);
	}
};
