/*
 * jLinq 2.1.0 (Packed)
 * ---------------------------------
 * Hugo Bonacci (webdev_hb@yahoo.com)
 * www.hugoware.net
 * License: Attribution-Share Alike
 * http://creativecommons.org/licenses/by-sa/3.0/us/
 */
 
var jLinq;
(function() {

	//contains functionality to create a new library for jLinq
	var library = function(settings) {
		var _jLinq = this;
		var _p = {};
		_p.settings = settings;

		//locks a jLinq object entirely
		_jLinq.finish = function(lock) {
			_jLinq.finish = null;
			_p.loaded = true;
			_p.lock = lock;
		};

		//utilities
		_p.util = {
			format:function(msg,args) {
				return msg.toString().replace(/%[0-9]+%/gi, function(match) {
					var index = parseInt(match.replace(/%/gi, ""));
					return args[index];
				});
			},
			allValues:function(array) {
				var actual = [];
				for(var i = 0; i < array.length; i++) {
					if (array[i] == null) { continue; }
					actual.push(array[i]);
				}
				return actual;
			},
			trim:function(val) {
				if (val == null) { return ""; }
				return val.toString().replace(/^\s+|\s+$/, "");
			},
			empty:function(array) {
				for(var i = 0; i < array.length; i++) {
					if (array[i]) { return false; }
				}
				return true;
			},
			type:function(val) {

				//if null, return null
				if (val == null) { return "null"; }
				if (val == undefined) { return "null"; }

				//check each type that has been saved
				for (var item in _p.types) {
					try {
						if (_p.types[item](val)) { return item; }
					}
					catch (e) {
						//no real concern here
					}
				}

				//if nothing was found, just return the typeof value
				return (typeof(val)).toString().toLowerCase();

			},
			when:function(val, actions) {
				var type = _p.util.type(val);
				if (!actions[type]) { 
					if (actions.empty && (val == null || val == undefined)) { return actions.empty(val); }
					if (actions.other) { return actions.other(val); }
					return false; 
				}
				try {
					return actions[type](val);
				}
				catch (e) {
					return false;
				}
			},
			each:function(array, action) { 
				var results = [];
				for (var i = 0; i < array.length; i++) {
					try {
						results.push(action(array[i], i)); 
					}
					catch(e) {
						results.push(e);
					}
				}
				return results;
			},
			clone: function(obj) {
				function gen(){};
				gen.prototype = obj;
				return new gen();
			}
		};

		//Types to evaluate for using .when()
		_jLinq.addType = function(name, compare) {
			name = _p.util.trim(name).toLowerCase();
			_p.types[name] = compare;
		};
		_jLinq.removeType = function(name) {
			name = _p.util.trim(name).toLowerCase();
			_p.types[name] = function() { return false; };
		};
		_p.types = settings.types ? settings.types : {};

		//Extends functionality onto the jLinq object
		//===============================================================================
		_jLinq.extend = function(params) {
			params.name = _p.util.trim(params.name);
			params.namespace = _p.util.trim(params.namespace);

			//check if this is locked
			if (_p.extend.hasCmd(params)) { 
				if (_p.lock) { throw "Exception: Library is locked."; }
				_p.extend.removeCmd(params);
			}

			//add this command to the list
			_p.extend.addCmd(params);

			//source extensions are evaluated immediately
			if (params.type.match(/source/i)) {

				//determine the correct source
				if (params.namespace && !_jLinq[params.namespace]) { _jLinq[params.namespace] = {}; }
				var target = params.namespace == "" ? _jLinq : _jLinq[params.namespace];

				//apply this command
				target[params.name] = function(v1,v2,v3,v4,v5,v6,v7,v8,v9,v10,v11,v12,v13,v14,v15,v16,v17,v18,v19,v20,v21,v22,v23,v24,v25) {

					//prepare the command for use
					var results = params.method({
						helper:_p.util
					}, v1,v2,v3,v4,v5,v6,v7,v8,v9,v10,v11,v12,v13,v14,v15,v16,v17,v18,v19,v20,v21,v22,v23,v24,v25);

					//make sure this is an array
					if (_p.util.type(results) != "array") {
						throw "Exception: A 'Source' extension must return an array for a jLinq query.";
					}

					//if this should generate a source
					return new _p.query(results);

				};

			}

		};
		_p.extend = {
			cmd:[],
			hasCmd:function(params) {
				return (_p.extend.findCmd(params) != null);
			},
			addCmd:function(params) {
				_p.extend.removeCmd(params);
				_p.extend.cmd.push(params);
			},
			removeCmd:function(params) {
				var index = _p.extend.findCmd(params);
				if (index) { 
					_p.extend.cmd.splice(index, 1); 
				}

			},
			findCmd:function(params) {
				for(var i = 0; i < _p.extend.cmd.length; i++) {
					var method = _p.extend.cmd[i];
					if (method.name == params.name && method.namespace == params.namespace) {
						return i;
					}
				}
				return null;
			}
		};

		//return a list of the commands available
		_jLinq.showCommands = function() {
			return _p.util.clone(_p.extend.cmd);
		};

		//add all of the current extension methods
		for (var item in settings.extend) {
			_jLinq.extend(settings.extend[item]);
		}

		//Generates the actual query for use
		//===============================================================================
		_p.query = function(data) {
			var _query = this;

			//duplicate this object
			data = _p.util.clone(data);

			//State of the query
			var _s = {};
			_s.state = {
				properties:true,
				lastCommand:null,
				lastField:null,
				lastCommandName:null,
				paramCount:0,
				ignoreCase:true,
				or:false,
				not:false,
				data:data,
				useProperties:false,
				operator:"",
				debug:{
					onEvent:function(msg) { },
					log:function(msg,args) {
						_s.state.debug.onEvent(_p.util.format(msg,args));
					}
				}
			};

			//determine the type of data this is
			if (data == null) { return null; }
			if (_p.util.type(data) == "array" && data.length > 0) {
				_s.state.useProperties = (_p.util.type(data[0]) == "object");
			}

			//Query evaluation
			_s.query = {
				cache:[],
				str:[],
				appendCmd:function(action) {
					_s.query.cache.push(action);

					//set the true or false value
					var not = _s.state.not ? "!" : "";

					//undo any state changes
					_s.state.or = false;
					_s.state.not = false;

					//append the items
					_s.query.str.push([_s.state.operator, "(", not, "(_s.query.cache[", (_s.query.cache.length - 1), "](record)))"].join(""));

					//update the operator as needed
					_s.state.operator = "&&";

				},
				select:function() {

					//if there hasn't been a command, return everything
					if (_s.query.str.length == 0) { 
						return {
							selected:_s.state.data,
							remaining: []
						};
					}

					//get the query string
					var query;
					eval(["query = function(record) {" + " return (", _s.query.str.join(""), "); };" ].join(""));

					//eval and select each result
					var selected = []; var remaining = [];
					for(var rec in _s.state.data) {
						var item = _s.state.data[rec];
						try {
							if (query(item)) {
								selected.push(item);
							}
							else {
								remaining.push(item);
							}
						}
						catch (e) {
							_s.state.debug.log("Exception when evaluating the query for selection: %0%.", [e]);
							remaining.push(item);
						}
					}

					//return the final results
					return {
						selected:selected,
						remaining:remaining
					};

				},
				prepCmd:function(params, args) {

					//prepare this command
					//set the known data
					_s.state.lastCommand = params.command;
					_s.state.paramCount = params.count;
					_s.state.lastCommandName = params.name;

					//start by clearing any null data
					var values = []; var found = false;
					for (var i = args.length; i-- > 0;) {
						if (!args[i] && !found) { continue; }
						found = true;
						values.push(args[i]);
					}
					values.reverse();

					//detetmine if a field was set in this
					//if the data list count is greater than
					//the required parameters, then the first
					//param should be the name of the field
					if (_s.state.useProperties && values.length == params.count + 1) {
						_s.state.lastField = values.shift();
					}

					//return an object to execute with
					return {
						arg: values,
						field: _s.state.lastField
					};
				},
				repeatCmd:function(v1,v2,v3,v4,v5,v6,v7,v8,v9,v10,v11,v12,v13,v14,v15,v16,v17,v18,v19,v20,v21,v22,v23,v24,v25) {
					if (_s.helper.empty([v1,v2,v3,v4,v5,v6,v7,v8,v9,v10,v11,v12,v13,v14,v15,v16,v17,v18,v19,v20,v21,v22,v23,v24,v25])) { return; }
					_s.state.lastCommand(v1,v2,v3,v4,v5,v6,v7,v8,v9,v10,v11,v12,v13,v14,v15,v16,v17,v18,v19,v20,v21,v22,v23,v24,v25);
				},
				performSort:function(records, field, desc) {
					records.sort(function(a,b) {
						a = a[field];
						b = b[field];
						return (a < b) ? -1 : (a > b) ? 1 : 0;
					});
					if (desc) { records.reverse(); }
				}
			};

			//Helper methods
			_s.helper = {
				trim:_p.util.trim,
				match:function(val,exp) {
					if (!(val && exp)) { return false; }
					if (_s.helper.type(exp) == "regexp") { exp = exp.source; }
					exp =  new RegExp(exp, "g"+(_s.state.ignoreCase?"i":""));
					return (val.match(exp));
				},
				equals:function(val1,val2) {
					try {

						//check for null values first
						if (val1 == null && val2 == null) { return true; }
						if ((val1 == null && val2) || (val1 && val2 == null)) { return false; }

						//if this is a string, check for case
						if (_s.helper.type(val1) == "string" && _s.helper.type(val2) == "string") {
							return _s.helper.match(val1, "^"+val2+"$");
						}
						if (_s.helper.type(val1) == "string" &&  _s.helper.type(val2) == "regexp") {
							return _s.helper.match(val1, val2);
						}
						else {
							return (val1 == val2);
						}
					}
					catch (e) {
						return false;
					}
				},
				allEqual:function(val1,val2) {
					if (_p.helper.type(val1) != "array") { val1 = [val1]; }
					for (var item in val1) {
						if (!_p.helper.equals(val1[item], val2)) { return false; }
					}
					return true;
				},
				anyEqual:function(val1,val2) {
					if (_p.helper.type(val1) != "array") { val1 = [val1]; }
					for (var item in val1) {
						if (!_p.helper.equals(val1[item], val2)) { return true; }
					}
					return false;
				},
				sort:function(records, sorting, desc) {

					//if no sorting was provided
					if (sorting == null) {
						records.sort();
						if (desc) { query.state.data.reverse(); }
						return records;
					}

					//recursively handle sorting
					var index = 0;
					var doSort = function(records) {  

						//clone to fix a BIZZARE IE7 bug					  
						records = _p.util.clone(records);

						var field = sorting[index].field;
						var desc = sorting[index].desc;

						//if at the end, just sort it
						if (index == sorting.length - 1) {
							_s.query.performSort(records, field, desc);
							return records;
						};

						//increment forward to the next command
						_s.query.performSort(records, field, desc);
						var dist = _s.helper.distinct(records, field);
						index++;

						//sort and gather values - call _doSort again if 
						//to check for futher commands
						var results = [];
						for (var j = 0; j < dist.length; j++) {
							var sorted = doSort(dist[j].items);
							for (var k = 0; k < sorted.length; k++) {
								results.push(sorted[k]);
							}
						};

						//return the results for this section
						return results;

					};
					return doSort(_s.state.data);


				},
				distinct:function(records, field) {
					var dist = {};
					for (var rec in records) {
						var val = records[rec];
						var key = (field != null) ? eval(["(val.", field, ")"].join("")) : val;

						//check if the value exists yet
						if (dist[key]) {
							dist[key].items.push(val);
						}
						else {
							dist[key] = {
								key:key,
								items:[ val ]
							};
						}
					}

					//return the final object
					var results = [];
					for (var item in dist) {
						results.push(dist[item]);
					}
					return results;
				},
				empty:_p.util.empty,
				type:_p.util.type,
				when:_p.util.when,
				each:_p.util.each,
				format:_p.util.format,
				clone:_p.util.clone,
				all:_p.util.allValues
			};

			//apply each of the extended functions
			for(var item in _p.extend.cmd) {
				(function(params) {

					//make sure this works
					if (!(params.type || params.name || params.method)) { return; }

					//source commands are not added to a query
					if (params.type.match(/source/i)) { return; }

					//determine the correct source
					if (params.namespace && !_query[params.namespace]) {  _query[params.namespace] = {}; }
					var target = params.namespace ? _query[params.namespace] : _query;

					//apply this command
					var method = (function(v1,v2,v3,v4,v5,v6,v7,v8,v9,v10,v11,v12,v13,v14,v15,v16,v17,v18,v19,v20,v21,v22,v23,v24,v25) {

						//set the state of the command
						_s.state.debug.log("Called command %0% '%1%()'.", [params.type, params.name]);
						var state = {
							add:function(cmd) { _s.query.str.push(cmd); },
							query:_query,
							state:_s.state,
							helper:_s.helper,
							repeat:function(v1,v2,v3,v4,v5,v6,v7,v8,v9,v10,v11,v12,v13,v14,v15,v16,v17,v18,v19,v20,v21,v22,v23,v24,v25) {
								_s.query.repeatCmd(v1,v2,v3,v4,v5,v6,v7,v8,v9,v10,v11,v12,v13,v14,v15,v16,v17,v18,v19,v20,v21,v22,v23,v24,v25);
							}
						};

						//depending on the type, set how this acts
						if (params.type.match(/^query$/i)) {

							//prepare this command for use
							var cmd = _s.query.prepCmd({
								command:target[params.name],
								count:params.count,
								name:params.name
							}, [v1,v2,v3,v4,v5,v6,v7,v8,v9,v10,v11,v12,v13,v14,v15,v16,v17,v18,v19,v20,v21,v22,v23,v24,v25]);

							//prepare the command for use
							_s.query.appendCmd(function(record) {

								//try and get the value
								try {
									state.value = _s.state.useProperties ? eval("record."+cmd.field) : record;
								}
								catch (e) {
									_s.state.debug.log("Exception when calling '%0%()' : %1%.", [params.name, e]);
									state.value = null;
								}

								//prepare the command
								state.record = record;
								state.type = state.helper.type(state.value);
								state.when = function(actions) {
									return _s.helper.when(state.value, actions);
								};

								//query the method
								return params.method(state, cmd.arg[0], cmd.arg[1], cmd.arg[2], cmd.arg[3], cmd.arg[4],
									cmd.arg[5], cmd.arg[6], cmd.arg[7], cmd.arg[8], cmd.arg[9], cmd.arg[10], cmd.arg[11], cmd.arg[12], cmd.arg[13], cmd.arg[14], 
									cmd.arg[15], cmd.arg[16], cmd.arg[17], cmd.arg[18], cmd.arg[19], cmd.arg[20], cmd.arg[21], cmd.arg[22], cmd.arg[23], cmd.arg[24]);
							});

							//return the query object
							return _query;

						}

						//if an action, do the action and return the query
						else if (params.type.match(/^action$/i)) {

							//execute the method and return the query
							try {
								params.method(state, v1,v2,v3,v4,v5,v6,v7,v8,v9,v10,v11,v12,v13,v14,v15,v16,v17,v18,v19,v20,v21,v22,v23,v24,v25);
							}
							catch (e) {
								_s.state.debug.log("Exception when calling '%0%()' : %1%.", [params.name, e]);
							}
							return _query;
						}

						//if selecting, return whatever the method returns
						else if (params.type.match(/^selection$/i)) {

							//if this query wants to manually select the results
							state.results = params.manual ? [] : _s.query.select();
							state.select = _s.query.select;

							//execute the selection method
							return params.method(state, v1,v2,v3,v4,v5,v6,v7,v8,v9,v10,v11,v12,v13,v14,v15,v16,v17,v18,v19,v20,v21,v22,v23,v24,v25);

						}

						//return the query
						return _query;

					});


					//next, assign a regular version
					target[params.name] = method;

					//next, query methods receive extra names
					if (params.type.match(/^query$/i) && 
						(_p.settings.generate == null || _p.settings.generate) && 
						(params.generate == null || params.generate)) {

						//then assign a second "or" version
						var altName = params.name.substr(0,1).toUpperCase() + params.name.substr(1, params.name.length - 1);

						//create an automatic OR version
						target["or"+altName] = function(v1,v2,v3,v4,v5,v6,v7,v8,v9,v10,v11,v12,v13,v14,v15,v16,v17,v18,v19,v20,v21,v22,v23,v24,v25) {
							_s.state.operator = "||";
							return method(v1,v2,v3,v4,v5,v6,v7,v8,v9,v10,v11,v12,v13,v14,v15,v16,v17,v18,v19,v20,v21,v22,v23,v24,v25);
						};

						//create an automatic AND version
						target["and"+altName] = function(v1,v2,v3,v4,v5,v6,v7,v8,v9,v10,v11,v12,v13,v14,v15,v16,v17,v18,v19,v20,v21,v22,v23,v24,v25) {
							_s.state.operator = "&&";
							return method(v1,v2,v3,v4,v5,v6,v7,v8,v9,v10,v11,v12,v13,v14,v15,v16,v17,v18,v19,v20,v21,v22,v23,v24,v25);
						};

						//create an automatic NOT version
						target["not"+altName] = function(v1,v2,v3,v4,v5,v6,v7,v8,v9,v10,v11,v12,v13,v14,v15,v16,v17,v18,v19,v20,v21,v22,v23,v24,v25) {
							_s.state.not = !_s.state.not;
							return method(v1,v2,v3,v4,v5,v6,v7,v8,v9,v10,v11,v12,v13,v14,v15,v16,v17,v18,v19,v20,v21,v22,v23,v24,v25);
						};

						//create an automatic NOT version
						target["andNot"+altName] = function(v1,v2,v3,v4,v5,v6,v7,v8,v9,v10,v11,v12,v13,v14,v15,v16,v17,v18,v19,v20,v21,v22,v23,v24,v25) {
							_s.state.not = !_s.state.not;
							_s.state.operator = "&&";
							return method(v1,v2,v3,v4,v5,v6,v7,v8,v9,v10,v11,v12,v13,v14,v15,v16,v17,v18,v19,v20,v21,v22,v23,v24,v25);
						};

						//create an automatic NOT version
						target["orNot"+altName] = function(v1,v2,v3,v4,v5,v6,v7,v8,v9,v10,v11,v12,v13,v14,v15,v16,v17,v18,v19,v20,v21,v22,v23,v24,v25) {
							_s.state.not = !_s.state.not;
							_s.state.operator = "||";
							return method(v1,v2,v3,v4,v5,v6,v7,v8,v9,v10,v11,v12,v13,v14,v15,v16,v17,v18,v19,v20,v21,v22,v23,v24,v25);
						};

					}

				})(_p.extend.cmd[item]);

			}

		};

	};

	//creates a default jLinq library
	var _defaultLibrary = function() { 
		return {
			locked:true,
			generate:true,
			types:{
				array:function(val) {
					return (val.push && val.pop && val.reverse && val.slice && val.splice);
				},
				string:function(val) {
					return ((typeof(val)).toString().match(/^string$/i));
				},
				number:function(val) {
					return ((typeof(val)).toString().match(/^number$/i));
				},
				bool:function(val) {
					return ((typeof(val)).toString().match(/^boolean$/i));
				},
				regexp:function(val) {
					return (val.ignoreCase != null && val.global != null && val.exec);
				},
				date:function(val) {
					return (val.getTime && val.setTime && val.toDateString && val.toTimeString);
				}
			},
			extend:[

				//Selection Methods
				//============================================================

				//default selection routine - selects from an array
				{name:"from", type:"source",
					method:function(query, source) {
						return source;
					}},


				//Action Methods
				//============================================================

				//enters into debug mode - options available
				{name:"debug", type:"action", operators:false,
					method:function(query, delegate) {
						query.state.debug.onEvent = delegate;
					}},

				//makes comparisons ignore case
				{name:"ignoreCase", type:"action",
					method:function(query) {
						query.state.ignoreCase = true;
					}},

				//makes string comparisons case sensitive
				{name:"useCase", type:"action",
					method:function(query) {
						query.state.ignoreCase = false;
					}},

				//flags the query for || - can be used to repeat a command
				{name:"or", type:"action",
					method:function(query, v1,v2,v3,v4,v5,v6,v7,v8,v9,v10,v11,v12,v13,v14,v15,v16,v17,v18,v19,v20,v21,v22,v23,v24,v25) {
						query.state.operator = "||";
						query.repeat(v1,v2,v3,v4,v5,v6,v7,v8,v9,v10,v11,v12,v13,v14,v15,v16,v17,v18,v19,v20,v21,v22,v23,v24,v25);
					}},

				//flags the query for ! - can be used to repeat a command
				{name:"not", type:"action",
					method:function(query, v1,v2,v3,v4,v5,v6,v7,v8,v9,v10,v11,v12,v13,v14,v15,v16,v17,v18,v19,v20,v21,v22,v23,v24,v25) {
						query.state.not = !query.state.not;
						query.repeat(v1,v2,v3,v4,v5,v6,v7,v8,v9,v10,v11,v12,v13,v14,v15,v16,v17,v18,v19,v20,v21,v22,v23,v24,v25);
					}},

				//flags the query for && - can be used to repeat a command
				{name:"and", type:"action",
					method:function(query, v1,v2,v3,v4,v5,v6,v7,v8,v9,v10,v11,v12,v13,v14,v15,v16,v17,v18,v19,v20,v21,v22,v23,v24,v25) {
						query.state.operator = "&&";
						query.repeat(v1,v2,v3,v4,v5,v6,v7,v8,v9,v10,v11,v12,v13,v14,v15,v16,v17,v18,v19,v20,v21,v22,v23,v24,v25);
					}},

				//flags the query for || and ! - can be used to repeat a command
				{name:"orNot", type:"action",
					method:function(query, v1,v2,v3,v4,v5,v6,v7,v8,v9,v10,v11,v12,v13,v14,v15,v16,v17,v18,v19,v20,v21,v22,v23,v24,v25) {
						query.state.or = true;
						query.state.not = !query.state.not;
						query.repeat(v1,v2,v3,v4,v5,v6,v7,v8,v9,v10,v11,v12,v13,v14,v15,v16,v17,v18,v19,v20,v21,v22,v23,v24,v25);
					}},

				//flags the query for && and ! - can be used to repeat a command
				{name:"andNot", type:"action",
					method:function(query, v1,v2,v3,v4,v5,v6,v7,v8,v9,v10,v11,v12,v13,v14,v15,v16,v17,v18,v19,v20,v21,v22,v23,v24,v25) {
						query.state.or = false;
						query.state.not = !query.state.not;
						query.repeat(v1,v2,v3,v4,v5,v6,v7,v8,v9,v10,v11,v12,v13,v14,v15,v16,v17,v18,v19,v20,v21,v22,v23,v24,v25);
					}},

				//combines all query selections into an expression with &&
				{name:"combine", type:"action",
					method:function(query, delegate) {
						query.add(query.state.operator+"("+(query.state.not?"!":""));
						query.state.operator = "";
						delegate(query.query);
						query.add(")");
					}},

				//combines all query selections into an expression with ||
				{name:"orCombine", type:"action",
					method:function(query, delegate) {
						query.state.operator = "||";
						query.add(query.state.operator+"("+(query.state.not?"!":""));
						query.state.operator = "";
						delegate(query.query);
						query.add(")");
					}},


				//Query Methods
				//=============================================================

				//runs a delegate as a query, this method is definately cheating
				//since -1 is telling it not to expect a field...
				{name:"where", count:-1, type:"query",
					method:function(query, delegate) {
						return delegate(query.record, query.helper);
					}},

				//checks if two fields are equal or not
				{name:"equals", count:1, type:"query",
					method:function(query, value) {
						return query.helper.equals(query.value, value);
					}},

				//returns if a string starts with the phrase
				//returns if the first element in array is equal
				{name:"startsWith", count:1, type:"query",
					method:function(query, value) {

						//allow arrays to be passed as comparisons
						if (query.helper.type(value) != "array") {
							value = [value];
						}

						//check for each value
						for (var item in value) {
							var match = value[item];
							if  (query.when({
								array:function() {
									return query.helper.equals(query.value[0], match)
								},
								other:function() {
									return query.helper.match(query.value.toString(), "^"+match.toString());
								}
							})) { return true; };
						}
					}},

				//returns if a string ends with a phrase
				//returns if the last element in array is equal
				{name:"endsWith", count:1, type:"query",
					method:function(query, value) {

						//allow arrays to be passed as comparisons
						if (query.helper.type(value) != "array") {
							value = [value];
						}

						//check for each value
						for (var item in value) {
							var match = value[item];
							if  (query.when({
								array:function() {
									return query.helper.equals(query.value[query.value.length - 1], match)
								},
								other:function() {
									return query.helper.match(query.value.toString(), match.toString()+"$");
								}
							})) { return true; };
						}
					}},

				//returns if a string contains a phrase
				//returns if any element in array is equal
				{name:"contains", count:1, type:"query",
					method:function(query, value) {
						if (value == null) { return false; }

						//allow arrays to be passed as comparisons
						if (query.helper.type(value) != "array") {
							value = [value];
						}

						//check for each value
						for (var item in value) {
							var match = value[item];
							if  (query.when({
								array:function() {
									for (var i = 0; i < query.value.length; i++) {
										if (query.helper.equals(query.value[i], match)) { return true; }
									}
								},
								other:function() {
									return query.helper.match(query.value.toString(), "^.*" + match.toString() + ".*$");
								}
							})) { return true; };
						}
					}},

				//evaluates each item with a regular expression
				{name:"match", count:1, type:"query",
					method:function(query, value) {

						//allow arrays to be passed as comparisons
						if (query.helper.type(value) != "array") {
							value = [value];
						}

						//check for each value
						for (var item in value) {
							var match = value[item];
							if  (query.when({
								array:function() {
									for (var i = 0; i < query.value.length; i++) {
										if (query.helper.match(query.value[i], match)) { return true; }
									}
								},
								other:function() {
									return query.helper.match(query.value.toString(), match);
								}
							})) { return true; };
						}

					}},

				//returns if a number is less
				//returns if a string has less characters
				//returns if an array has less elements
				{name:"less", count:1, type:"query",
					method:function(query, value) {
						return query.when({
							number:function() {
								return (query.value < value);
							},
							string:function() {
								return (query.value.length < value.toString().length);
							},
							array:function() {
								return query.helper.when(value, {
									number:function() {
										return (query.value.length < value);
									},
									array:function() {
										return (query.value.length < value.length);
									}
								});
							},
							other:function() {
								return (query.value < value);
							}
						});
					}},

				//returns if a number is more
				//returns if a string has more characters
				//returns if an array has more elements
				{name:"greater", count:1, type:"query",
					method:function(query, value) {
						return query.when({
							number:function() {
								return (query.value > value);
							},
							string:function() {
								return (query.value.length > value.toString().length);
							},
							array:function() {
								return query.helper.when(value, {
									number:function() {
										return (query.value.length > value);
									},
									array:function() {
										return (query.value.length > value.length);
									}
								});
							},
							other:function() {
								return (query.value > value);
							}
						});
					}},

				//returns if a number is less
				//returns if a string has less characters
				//returns if an array has less elements
				{name:"lessEquals", count:1, type:"query",
					method:function(query, value) {
						return query.when({
							number:function() {
								return (query.value <= value);
							},
							string:function() {
								return (query.value.length <= value.toString().length);
							},
							array:function() {
								return query.helper.when(value, {
									number:function() {
										return (query.value.length <= value);
									},
									array:function() {
										return (query.value.length <= value.length);
									}
								});
							},
							other:function() {
								return (query.value <= value);
							}
						});
					}},

				//returns if a number is more
				//returns if a string has more characters
				//returns if an array has more elements
				{name:"greaterEquals", count:1, type:"query",
					method:function(query, value) {
						return query.when({
							number:function() {
								return (query.value >= value);
							},
							string:function() {
								return (query.value.length >= value.toString().length);
							},
							array:function() {
								return query.helper.when(value, {
									number:function() {
										return (query.value.length >= value);
									},
									array:function() {
										return (query.value.length >= value.length);
									}
								});
							},
							other:function() {
								return (query.value >= value);
							}
						});
					}},

				//returns if a number is more
				//returns if a string has more characters
				//returns if an array has more elements
				{name:"between", count:2, type:"query",
					method:function(query, low, high) {
						return query.when({
							number:function() {
								return (query.value > low && query.value < high);
							},
							other:function() {
								//arrays and strings
								return (query.value.length > low && query.value.length < high);
							}
						});
					}},

				//returns if a number is more
				//returns if a string has more characters
				//returns if an array has more elements
				{name:"betweenEquals", count:2, type:"query",
					method:function(query, low, high) {
						return query.when({
							number:function() {
								return (query.value >= low && query.value <= high);
							},
							other:function() {
								//arrays and strings
								return (query.value.length >= low && query.value.length <= high);
							}
						});
					}},

				//returns if a field is null
				//returns if an array is empty
				//returns if a string is empty
				{name:"empty", count:0, type:"query",
					method:function(query) {
						return query.when({
							array:function() {
								return (query.value.length == 0);
							},
							string:function() {
								return (query.value == "");
							},
							empty:function() {
								return true;
							}
						});
					}},

				//returns if a boolean iss true
				//returns if a record has a field
				{name:"is", count:0, type:"query",
					method:function(query) {
						return query.when({
							bool:function() {
								return query.value;
							},
							empty:function() {
								return false;
							},
							other:function() {
								return (query.value != null);
							}
						});
					}},

				//returns if a boolean is false
				//returns if a record is missing a field
				{name:"isNot", count:0, type:"query",
					method:function(query) {
						return query.when({
							bool:function() {
								return !query.value;
							},
							empty:function() {
								return true;
							},
							other:function() {
								return (query.value == null);
							}
						});
					}},


				//Selection Methods 
				//=============================================================

				//returns if any records match the query
				{name:"any", type:"selection",
					method:function(query) {
						return (query.query.count() > 0);
					}},

				//returns if all records match the query
				{name:"all", type:"selection",
					method:function(query) {
						return (query.results.selected.length == query.state.data.length);
					}},

				//returns none of the records match
				{name:"none", type:"selection",
					method:function(query) {
						return !query.query.all();
					}},

				//returns the total records found
				{name:"count", type:"selection",
					method:function(query, invert) {
						return invert ? query.results.remaining.length : query.results.selected.length;
					}},

				//returns array of matching records
				{name:"select", type:"selection",
					method:function(query, selection, invert) {

						//select the records
						var records = [];
						var results = invert ? query.results.remaining : query.results.selected;
						selection = query.helper.type(selection) == "function" ? selection : function(r) { return r; };
						for (var i = 0; i < results.length; i++) {
							records.push(selection(results[i]));
						}

						//return the final records
						return records;

					}},

				//returns HTML string for a table
				{name:"toTable", type:"selection", manual:true,
					method:function(query, params, selection, invert) {
						params = params ? params : {};
						var results = query.query.select(selection, invert);

						//create the table structure
						if (results.count == 0) { return "No results for this query"; }

						//getting a string
						var getString = function(raw) {
							query.helper.when(raw, {
								date:function() {
									raw = query.helper.format("%0%/%1%/%2% at %3%:%4% %5%", 
										[raw.getMonth()+1, raw.getDate(), raw.getFullYear(), (raw.getHours() > 12 ? raw.getHours() - 12 : raw.getHours()),  raw.getMinutes(), (raw.getHours() > 12 ? "PM" : "AM")]
										);
								},
								empty:function() {
									raw = "null";
								},
								other:function() {
									raw = raw.toString();
								}
							});
							return raw;
						};

						//otherwise, start the table
						var output = ["<table cellpadding='0' cellspacing='0' " + 
							(params.border?"border='" + params.border + "' ":"") + 
							(params.css?"class='" + params.css + "' ":"") + " >"];

						//create the header
						if (query.state.useProperties) {
							var columns = [];
							output.push("<tr>");
							for(var item in results[0]) {
								columns.push(item);
								output.push("<th>");
								output.push(escape(item));
								output.push("</th>");
							}
							output.push("</tr>");
						}

						//do each item
						var alt = true;
						for (var i = 0; i < results.length; i++) {
							alt = !alt;
							var record = results[i];
							output.push("<tr " + (alt?"class='alt'":"") + ">");

							//create the row
							if (query.state.useProperties) {
								for(var col in columns) {

									//get a formatted string
									var item = columns[col];
									var val = record[item];
									var msg = getString(val);

									//add the information
									output.push("<td>");
									output.push(msg);
									output.push("</td>");
								}
							}
							else {
								//no properties
								output.push("<td>");
								output.push(getString(record));
								output.push("</td>");
							}

							//close the row
							output.push("</tr>");

						}

						//close the table and return the output
						output.push("</table>");
						return output.join("");

					}},

				//Executes the action to each match then returns the query - technically a selection
				{name:"each", type:"selection", manual:true,
					method:function(query, action, selection, invert) {

						//select the correct records and perform the action
						var results = query.query.select(selection, invert);
						for(var i = 0; i < results.length; i++) {
							action(results[i], i);
						}

						//return the query again
						return query.query;

					}},

				//Orders the records then returns the query - technically a selection
				{name:"orderBy", type:"selection", manual:true,
					method:function(query, v1,v2,v3,v4,v5,v6,v7,v8,v9,v10,v11,v12,v13,v14,v15,v16,v17,v18,v19,v20,v21,v22,v23,v24,v25) {
						var order = query.helper.all([v1,v2,v3,v4,v5,v6,v7,v8,v9,v10,v11,v12,v13,v14,v15,v16,v17,v18,v19,v20,v21,v22,v23,v24,v25]);
						if (order.length == 0) { order = [""]; }

						//sort depending on if there are properties or not
						if (!query.state.useProperties) {
							var desc = (order.length > 0) ? (order[0]+"").match(/^\-/g) : false;
							query.state.data = query.helper.sort(query.state.data, null, desc);
							return query.query;
						}

						//since this is more complicated, build the sorting order
						var sorting = [];
						for (var i = 0; i < order.length; i++) {
							sorting.push({ 
								desc: (order[i].substr(0,1) == "-"),
								field: order[i].replace(/^\-/g, "")
								});
						}
						query.state.data = query.helper.sort(query.state.data, sorting);

						//return a new query
						return query.query;

					}},

				//returns all distinct values based on the field
				{name:"distinct", type:"selection",
					method:function(query, field, invert) {
						var sel = invert ? query.results.remaining : query.results.selected;
						var results = query.helper.distinct(sel, field);

						//return just the names of the fields
						var dist = [];
						for (var item in results) {
							dist.push(results[item].key);
						}

						//sort it to be helpful
						return query.helper.sort(dist, null, false);

					}},

				//groups the records and returns 
				{name:"groupBy", type:"selection",
					method:function(query, field, invert) {
						var sel = invert ? query.results.remaining : query.results.selected;
						var results = query.helper.distinct(sel, field);
						return jLinq.from(results);
					}},

				//Joins a second array to the current query
				{name:"join", type:"selection", 
					method:function(query, source, alias, pk, fk) {

						//clone the source array
						source = query.helper.clone(source);

						//create a second query for this item
						var gen = [];
						for (var i = 0; i < query.state.data.length; i++) {
							var record = query.helper.clone(query.state.data[i]);
							var results = jLinq.from(source).equals(fk, record[pk]).select();
							if (results.length == 1) { 
								record[alias] = results[0];
							}
							else {
								record[i][alias] = results;
							}

							//add the new record
							gen.push(record);
						}

						//return a new query
						return jLinq.from(gen);

					}},

				//skips and takes the correct set of records
				{name:"skipTake", type:"selection", manual:true,
					method:function(query, skip, take, selection, invert) {
						skip = Math.max(query.helper.type(skip) == "number" ? skip : 0, 0);
						take = Math.max(query.helper.type(take) == "number" ? take : 0, 0);

						//take the correct number of records
						var results = query.query.select(selection, invert);
						return results.slice(skip, (skip + take));

					}},

				//skips and takes the correct set of records
				{name:"take", type:"selection", manual:true,
					method:function(query, take, selection, invert) {
						take = Math.max(query.helper.type(take) == "number" ? take : 0, 0);

						//take the correct number of records
						var results = query.query.select(selection, invert);
						return results.slice(0, take );

					}},

				//the first match of the set -- allows a default match if nothing is found
				{name:"first", type:"selection", manual:true,
					method:function(query, defType, selection, invert) {
						var results = query.query.select(selection, invert);
						return results.length > 0 ? results[0] : defType ? defType : null;
					}},

				//the last match of the set -- allows a default match if nothing is found
				{name:"last", type:"selection", manual:true,
					method:function(query, defType, selection, invert) {
						var results = query.query.select(selection, invert);
						return results.length > 0 ? results[results.length - 1] : defType ? defType : null;
					}},

				//the element at the specified index -- allows a default match if nothing is found
				{name:"at", type:"selection", manual:true,
					method:function(query, index, defType, selection, invert) {
						var results = query.query.select(selection, invert);
						return index < results.length || index >= 0 ? results[index] : defType ? defType : null;
					}}

			]

		}; 

	}; //default library

	//create the base library
	jLinq = new library(_defaultLibrary());
	jLinq.finish(true);

	//Create entirely new libraries
	jLinq.library = function(settings, imp) {
		if (imp == null) { imp = true; }

		//if no defaults, return it as is
		var lib = new library(_defaultLibrary());

		//clear out the info if not importing
		if (!imp) { 
			lib.types = {};
			lib.extend = [];
		}

		//import any settings, if any
		var lock = false;
		if (settings) {

			//extend the methods
			if (settings.extend) {
				for (var ext in settings.extend) {
					lib.extend(settings.extend[ext])
				}
			}

			//extend the types
			if (settings.types) {
				for (var type in settings.types) {
					lib.addType(settings.types[type]);;
				}
			}

			//if there is a lock setting
			if (settings.locked) { lock = settings.locked; }

		};

		//set the lock and return
		lib.finish(lock);
		return lib;

	};

})();
