
/* JSCORE */
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);
	}
};


/* Location Element copyright datingnode.com */

var LocationElement = jscore.def ('LocationElement', {

	LocationElement: function () {
		this.elementName = null;
		this.country = null;
		this.region = null;
		this.locality = null;

		this.countryBlank 	= 'Please Select';
		this.regionBlank 	= 'Please Select';
		this.localityBlank 	= 'Please Select';
		
	},
	init: function (htmlName, countryBlank, regionBlank, localityBlank) {
		var thisObj = this;
		this.elementName = htmlName;

		this.countryBlank 	= countryBlank;
		this.regionBlank 	= regionBlank;
		this.localityBlank 	= localityBlank;
		
		this.country = $("#"+this.elementName+"_country").val();
		this.region =  $("#"+this.elementName+"_region").val();
		this.locality =  $("#"+this.elementName+"_locality").val();

		if (!this.country)	{
			$("select#"+thisObj.elementName+"_region").attr("disabled", "disabled").addClass('disabled');
		}
		if (!this.region)	{
			$("select#"+thisObj.elementName+"_locality").attr("disabled", "disabled").addClass('disabled');
		}
		
		// add the magic calls...
		$("#"+this.elementName+"_country").change(function () { thisObj.countryChange(); });
		$("#"+this.elementName+"_region").change(function () { thisObj.regionChange(); });

	},
	countryChange: function () {
		var thisObj = this;
		this.country = $("#"+this.elementName+"_country").val();
		
		// setup the following selects
		this.clearSelect($("#"+this.elementName+"_region"), !this.country?thisObj.regionBlank:'Loading...');
		this.clearSelect($("#"+this.elementName+"_locality"), thisObj.localityBlank);
		$("select#"+thisObj.elementName+"_locality").attr("disabled", "disabled").addClass('disabled');
		$("select#"+thisObj.elementName+"_region").attr("disabled", "disabled").addClass('disabled');
		
		// if they selected blank then return
		if (!this.country) return;
		
		$("#"+this.elementName+"_regionLoading").show();
		$("select#"+thisObj.elementName+"_region").attr("disabled", "disabled").addClass('disabled');

		// query the regions...
		$.getJSON("/ajax/region_lookup__"+this.country+"/",
		        function(data){
					var options = '<option value="">'+thisObj.regionBlank+'</option>';	
			        $.each(data.items, function(i,item){
						options += '<option value="' + item.value + '">' + item.text + '</option>';	   
			        });  
					$("select#"+thisObj.elementName+"_region").html(options);
					
					
			        // hide loading message
					$("#"+thisObj.elementName+"_regionLoading").hide();
					$("select#"+thisObj.elementName+"_region").removeAttr("disabled").removeClass('disabled');
		        });
	},
	regionChange: function () {
		var thisObj = this;
		this.region = $("#"+this.elementName+"_region").val();
		this.clearSelect($("#"+this.elementName+"_locality"), !this.region?thisObj.localityBlank:'Loading...');
		$("select#"+thisObj.elementName+"_locality").attr("disabled", "disabled").addClass('disabled');
		
		if (!this.region) return;
		$("#"+this.elementName+"_localityLoading").show();
		
		// query the regions...
		$.getJSON("/ajax/locality_lookup/_"+this.country+"/_"+this.region+"/",
		        function(data){

					var options = '<option value="">'+thisObj.localityBlank+'</option>';	
			        $.each(data.items, function(i,item){
						options += '<option value="' + item.value + '">' + item.text + '</option>';	   
			        });  
					$("select#"+thisObj.elementName+"_locality").html(options);
					
			        // hide loading message
					$("#"+thisObj.elementName+"_localityLoading").hide();
					$("select#"+thisObj.elementName+"_locality").removeAttr("disabled").removeClass('disabled');
		        });
	},
	clearSelect: function (domObj, blank) {
		domObj.get(0).options.length = 0;
		if (blank) {
			var options = '<option value="">'+blank+'</option>';	
			$(domObj).html(options);
		}
		
	}
});


/* Timezone Element by copyright datingnode.com */

var TimezoneElement = jscore.def ('TimezoneElement', {

	TimezoneElement: function () {
	
		this.country = null;
		this.region = null;
		this.locality = null;
	},
	go: function (countryId, regionId, localityId) {
	
		if (!localityId) {
			this.clearSelect($("#"+this.elementName));
			this.close();
		}

		this.country = countryId;
		this.region = regionId;
		this.locality = localityId;
		this.getTimezones();
		
	},
	init: function (htmlName) {
		this.elementName = htmlName;
	},
	close: function () {
	 $("select#"+this.elementName).parent().parent().fadeOut("slow");
	},
	open: function () {
	
	 $("select#"+this.elementName).parent().parent().fadeIn("slow");
	},
	getTimezones: function () {
		var thisObj = this;
		
		// setup the following selects
		this.clearSelect($("#"+this.elementName));
		
		$("select#"+thisObj.elementName).attr("disabled", "disabled");
		$("#"+this.elementName+"_timezoneLoading").show();
		
		// query the regions...
		$.getJSON("/ajax/geo/timezone_query/_"+this.country+"/_"+this.region+"/_"+this.locality+"/",
		        function(data){
		        	if (data.items.length > 1) {	
			        	 thisObj.open();		        	
			        } else {
				       thisObj.close();
			        }
		        
			        
					var options = '<option value="">Please Select</option>';	
			        $.each(data.items, function(i,item){
						options += '<option value="' + item.value + '"'+(data.items.length==1?' selected="selected"':'')+'>' + item.text + '</option>';	   
			        });  
					$("select#"+thisObj.elementName).html(options);
					
			        // hide loading message
					$("#"+thisObj.elementName+"_timezoneLoading").hide();
					$("select#"+thisObj.elementName).removeAttr("disabled");
		        });
	},
	clearSelect: function (domObj) {
		domObj.get(0).options.length = 0;
		
	}
});

(function($){$.gritter={};$.gritter.options={fade_in_speed:'medium',fade_out_speed:1000,time:6000}
$.gritter.add=function(params){try{return Gritter.add(params||{});}catch(e){var err='Gritter Error: '+e;(typeof(console)!='undefined'&&console.error)?console.error(err,params):alert(err);}}
$.gritter.remove=function(id,params){Gritter.removeSpecific(id,params||{});}
$.gritter.removeAll=function(params){Gritter.stop(params||{});}
var Gritter={fade_in_speed:'',fade_out_speed:'',time:'',_custom_timer:0,_item_count:0,_is_setup:0,_tpl_close:'<div class="gritter-close"></div>',_tpl_item:'<div id="gritter-item-[[number]]" class="gritter-item-wrapper [[item_class]]" style="display:none"><div class="gritter-top"></div><div class="gritter-item">[[image]]<div class="[[class_name]]"><span class="gritter-title">[[username]]</span><p>[[text]]</p></div><div style="clear:both"></div></div><div class="gritter-bottom"></div></div>',_tpl_wrap:'<div id="gritter-notice-wrapper"></div>',add:function(params){if(!params.title||!params.text){throw'You need to fill out the first 2 params: "title" and "text"';}
if(!this._is_setup){this._runSetup();}
var user=params.title,text=params.text,image=params.image||'',sticky=params.sticky||false,item_class=params.class_name||'',time_alive=params.time||'';this._verifyWrapper();this._item_count++;var number=this._item_count,tmp=this._tpl_item;$(['before_open','after_open','before_close','after_close']).each(function(i,val){Gritter['_'+val+'_'+number]=($.isFunction(params[val]))?params[val]:function(){}});this._custom_timer=0;if(time_alive){this._custom_timer=time_alive;}
var image_str=(image!='')?'<img src="'+image+'" class="gritter-image" />':'',class_name=(image!='')?'gritter-with-image':'gritter-without-image';tmp=this._str_replace(['[[username]]','[[text]]','[[image]]','[[number]]','[[class_name]]','[[item_class]]'],[user,text,image_str,this._item_count,class_name,item_class],tmp);this['_before_open_'+number]();$('#gritter-notice-wrapper').append(tmp);var item=$('#gritter-item-'+this._item_count);item.fadeIn(this.fade_in_speed,function(){Gritter['_after_open_'+number]($(this));});if(!sticky){this._setFadeTimer(item,number);}
$(item).bind('mouseenter mouseleave',function(event){if(event.type=='mouseenter'){if(!sticky){Gritter._restoreItemIfFading($(this),number);}}
else{if(!sticky){Gritter._setFadeTimer($(this),number);}}
Gritter._hoverState($(this),event.type);});return number;},_countRemoveWrapper:function(unique_id,e){e.remove();this['_after_close_'+unique_id](e);if($('.gritter-item-wrapper').length==0){$('#gritter-notice-wrapper').remove();}},_fade:function(e,unique_id,params,unbind_events){var params=params||{},fade=(typeof(params.fade)!='undefined')?params.fade:true;fade_out_speed=params.speed||this.fade_out_speed;this['_before_close_'+unique_id](e);if(unbind_events){e.unbind('mouseenter mouseleave');}
if(fade){e.animate({opacity:0},fade_out_speed,function(){e.animate({height:0},300,function(){Gritter._countRemoveWrapper(unique_id,e);})})}
else{this._countRemoveWrapper(unique_id,e);}},_hoverState:function(e,type){if(type=='mouseenter'){e.addClass('hover');var find_img=e.find('img');(find_img.length)?find_img.before(this._tpl_close):e.find('span').before(this._tpl_close);e.find('.gritter-close').click(function(){var unique_id=e.attr('id').split('-')[2];Gritter.removeSpecific(unique_id,{},e,true);});}
else{e.removeClass('hover');e.find('.gritter-close').remove();}},removeSpecific:function(unique_id,params,e,unbind_events){if(!e){var e=$('#gritter-item-'+unique_id);}
this._fade(e,unique_id,params||{},unbind_events);},_restoreItemIfFading:function(e,unique_id){clearTimeout(this['_int_id_'+unique_id]);e.stop().css({opacity:''});},_runSetup:function(){for(opt in $.gritter.options){this[opt]=$.gritter.options[opt];}
this._is_setup=1;},_setFadeTimer:function(e,unique_id){var timer_str=(this._custom_timer)?this._custom_timer:this.time;this['_int_id_'+unique_id]=setTimeout(function(){Gritter._fade(e,unique_id);},timer_str);},stop:function(params){var before_close=($.isFunction(params.before_close))?params.before_close:function(){};var after_close=($.isFunction(params.after_close))?params.after_close:function(){};var wrap=$('#gritter-notice-wrapper');before_close(wrap);wrap.fadeOut(function(){$(this).remove();after_close();});},_str_replace:function(search,replace,subject,count){var i=0,j=0,temp='',repl='',sl=0,fl=0,f=[].concat(search),r=[].concat(replace),s=subject,ra=r instanceof Array,sa=s instanceof Array;s=[].concat(s);if(count){this.window[count]=0;}
for(i=0,sl=s.length;i<sl;i++){if(s[i]===''){continue;}
for(j=0,fl=f.length;j<fl;j++){temp=s[i]+'';repl=ra?(r[j]!==undefined?r[j]:''):r[0];s[i]=(temp).split(f[j]).join(repl);if(count&&s[i]!==temp){this.window[count]+=(temp.length-s[i].length)/f[j].length;}}}
return sa?s:s[0];},_verifyWrapper:function(){if($('#gritter-notice-wrapper').length==0){$('body').append(this._tpl_wrap);}}}})(jQuery);



/* ----- Main App */
var NodeClient = function (profileId, logging, displayPopups) { 

	this.status				= 'ready';
	this.clientId 			= null;
	this.apiErrorCodes		= null;
	this.profileId			= null;
	this.lastAccessed		= null;
	this.logging			= logging;
	this.pingCount			= 0;
	this.lastActionTimestamp= null;
	this.displayPopups 		= displayPopups;
	
	this.msgCountPersonal		= 0;
	this.msgCountRequest		= 0;
	this.msgCountNotification	= 0;
	
	this.init();
	
	setTimeout(function () { nodeClient.ping(); }, 10000);
	
	
};

NodeClient.prototype = {

	/* intalises the app for current user */
	init: function () {

		var that = this;
		this.setStatus('ready', 'Ready...');

		this.apiErrorCodes 	= {
       	   1 : 'BAD_LOGIN_DETAILS',
       	   2 : 'UNVERIFIED_PROFILE',
       	   3 : 'API_CLIENT_NOT_FOUND'
		};
		
		
	},

	/* communicates with the server */
	ping: function () {
		
		this.pingCount++;
		
		this.setStatus('communicating', 'Ping...');
		
		var that = this;
		
		$.ajax({url: '/ajax/ping/', 
			data: {clientId:this.clientId},
			type: 'POST',
			dataType: 'json',
			success: function(data){ 	

				if (data.status == true) {
			
					that.lastAccessed = data.accessed;
						
					if (data.actions.length) {
						that.log(data.actions);
						for (action in data.actions) {
	
							// TODO: Add popup here
							that.gotAction(data.actions[action]);
						}
					}
					that.updateMessageCount(data['msgCount']['personal'], data['msgCount']['request'], data['msgCount']['notification']);

					if (that.pingCount <= 3) delay = 10000; /* 10 secs : 30 seconds */
					else if (that.pingCount <= 7) delay = 15000; /* 15 secs : 1 mins */
					else if (that.pingCount <= 14) delay = 30000; /* 30 secs */
					else if (that.pingCount <= 19) delay = 60000; /* 1 mins */
					else delay = 300000; /* 5 mins */
					
					setTimeout(function () { nodeClient.ping() }, delay );

					setTimeout(function () { nodeClient.setStatus('connected', 'Ping... Pong!'); }, 700);
					setTimeout(function () { nodeClient.setStatus('connected', 'Connected'); }, 1500);
					
				} else {

					/* API client not found on server - need to log in */
					if (that.apiErrorCodes[data.errorCode] == 'API_CLIENT_NOT_FOUND') {
						that.setStatus('disconnected', 'Lost connection to site');
						
					} else {
						that.setStatus('error', 'Failed... (retrying)');
						setTimeout(function () { nodeClient.ping() }, 60000);
					}
				}
			},
			error: function (jqXHR, textStatus, errorThrown) {
				
				that.setStatus('error', 'Connecting (retrying)');
				
				setTimeout(function () { nodeClient.ping() }, 10000);
			}
		});

		
	},
	updateMessageCount: function (personal, request, notification) {
		
		this.msgCountPersonal		= personal;
		this.msgCountRequest		= request;
		this.msgCountNotification	= notification;
		
		elements = $('.msgCountPersonal');
		elements.each(function() { 
			if ($(this).html() != String(personal))
				$(this).fadeOut(300, 
						function () { $(this).html(personal);  if (personal) $(this).fadeIn(); }
				);
		});

		elements = $('.msgCountRequest');
		elements.each(function() { 
			if ($(this).html() != String(request))
				$(this).fadeOut(300, 
						function () { $(this).html(request); if (request) $(this).fadeIn(); }
				);
		});

		elements = $('.msgCountNotifcation');
		elements.each(function() { 
			if ($(this).html() != String(notification))
				$(this).fadeOut(300, 
						function () { $(this).html(notification); if (notification) $(this).fadeIn(); }
				);
		});
		
		var total = personal+request+notification;
		elements = $('.msgCountTotal');
		elements.each(function() { 
			if ($(this).html() != String(total))
				$(this).fadeOut(300, function () { $(this).html(total); if (total) $(this).fadeIn(); });
		});

	},

	setStatus: function (code, text) {
		this.status 		= code;
		this.statusText 	= text;
		this.log(code+': '+text);
		$('#apiConnection').html(text);
		switch (code) {
		case 'communicating':
			break;
		case 'disconnected':
		case 'error':
		//	chrome.browserAction.setIcon({path:'/img/icons/offline_19.png'});
			break;
		default:
		//	chrome.browserAction.setIcon({path:'/img/icons/19.png'});
			break;
		}
		
	},
	log: function (txt) {
		if(this.logging && typeof console != "undefined" && typeof console.log != "undefined") {
			console.log(txt);

		}
	},
	gotAction: function (action) {
		
		if (this.displayPopups == false) return;
		
		var type 			= action['type'];
		var username 		= action['username'];
		var title 			= action['title'];
		var body 			= action['body'];
		var thumbnail 		= action['thumbnail'];
		var uri 			= action['uri'];
		var timestamp 		= action['timestamp'];

		if (timestamp > this.lastActionTimestamp)
			this.lastActionTimestamp = timestamp;
		
		$.gritter.add({
			title: title,
			text: "<div style=\'padding-bottom:5px; font-style:italic; color:#ccc;\'>Message from <a href='/profile/"+username+"' style=\'color:#fff;\'>"+username+"</a></div>"+body+"<div style=\'text-align:right;padding:10px 10px 0 0;\'><a href=\'"+uri+"\' class=\'linkButton\'>View Message &raquo;</a></div>",
			image: thumbnail,
			sticky: true, 
			time: "",
			class_name: "my-sticky-class" 
		});
	}
	
};

	
	


$(document).ready (function () { 
	
	/* ie6 message */
	if(( $.browser.msie ) && ( $.browser.version < 7 )) {
		$('body').prepend('<br /><div id="ie6Warning" style="width:600px; margin:20px auto 20px; background-color:#eee; border:1px solid #aaa; padding:20px;">'+
			      '<h3>Time to upgrade your browser</h3>'+
			      '<p>If you\'re reading this, you\'re surfing using Internet Explorer 6, an eight-year-old browser that cannot cope with the demands of the modern internet. This site will not function correctly in this browser. For the best web experience, we strongly recommend upgrading to <a href="http://www.google.com/chrome">Google Chrome</a>, <a href="http://www.getfirefox.com/">Firefox</a>, <a href="http://www.opera.com/">Opera</a>, <a href="http://www.apple.com/safari/">Safari</a>, or a more recent version of <a href="http://www.microsoft.com/windows/downloads/ie/getitnow.mspx">Internet Explorer</a>.</p>' +
			      '</div>');
	}

	$('.boxTitleCollapsable').click(function() {
		var context = $(this);
		if (context.hasClass('boxTitleCollapsed')) {
			context.removeClass('boxTitleCollapsed');
			$('.boxCollapseContainer', context.parent()).slideDown(300);
		} else {
			context.addClass('boxTitleCollapsed');
			$('.boxCollapseContainer', context.parent()).slideUp(300);
		}
	});
	$('.collapsableParent').click(function() {
		var context = $(this);
		if (context.hasClass('contentCollapsed')) {
			context.removeClass('contentCollapsed');
			$('.collapsableChild', context.parent()).slideDown(300);
		} else {
			context.addClass('contentCollapsed');
			$('.collapsableChild', context.parent()).slideUp(300);
		}
	});
	
});

