define("dojox/data/AndOrReadStore", ["dojo/_base/declare", "dojo/_base/lang", "dojo/data/ItemFileReadStore", "dojo/data/util/filter", "dojo/_base/array", "dojo/_base/json"],
  function(declare, lang, ItemFileReadStore, filterUtil, array, json) {
  
// module:
//		dojox/data/AndOrReadStore
// summary:
//		TODOC

return declare("dojox.data.AndOrReadStore", [ItemFileReadStore], {
	// summary:
	//		AndOrReadStore uses ItemFileReadStore as a base, modifying only the query (_fetchItems) section.
	//		Supports queries of the form: query:"id:1* OR dept:'Sales Department' || (id:2* && NOT dept:S*)"
	//		Includes legacy/widget support via:
	// |		query:{complexQuery:"id:1* OR dept:'Sales Department' || (id:2* && NOT dept:S*)"}
	//		The ItemFileReadStore implements the dojo/data/api/Read API and reads
	//		data from JSON files that have contents in this format --
	// |	{ items: [
	// |		{ name:'Kermit', color:'green', age:12, friends:['Gonzo', {_reference:{name:'Fozzie Bear'}}]},
	// |		{ name:'Fozzie Bear', wears:['hat', 'tie']},
	// |		{ name:'Miss Piggy', pets:'Foo-Foo'}
	// |	]}
	//		Note that it can also contain an 'identifier' property that specified which attribute on the items
	//		in the array of items that acts as the unique identifier for that item.

	_containsValue: function(/*dojo/data/api/Item*/ item, /*attribute-name-string */ attribute, /*anything*/ value,
			/*String|RegExp?*/ regexp){
		// summary:
		//		Internal function for looking at the values contained by the item.
		// description:
		//		Internal function for looking at the values contained by the item.  This
		//		function allows for denoting if the comparison should be case sensitive for
		//		strings or not (for handling filtering cases where string case should not matter)
		// item:
		//		The data item to examine for attribute values.
		// attribute:
		//		The attribute to inspect.
		// value:
		//		The value to match.
		// regexp:
		//		Optional string or regular expression generated off value if value was of string type to handle wildcarding.
		//		If present and attribute values are string, then it can be used for comparison instead of 'value'
		//		If RegExp is a string, it is treated as an comparison statement and eval for number comparisons
		return array.some(this.getValues(item, attribute), function(possibleValue){
			// if string... eval for math operator comparisons
			if(lang.isString(regexp)){
				return eval(regexp);
			}else if(possibleValue !== null && !lang.isObject(possibleValue) && regexp){
				if(possibleValue.toString().match(regexp)){
					return true; // Boolean
				}
			} else if(value === possibleValue){
				return true; // Boolean
			} else {
				return false;
			}
		});
	},

	filter: function(requestArgs, arrayOfItems, findCallback){
		var items = [];
		if(requestArgs.query){
			//Complete copy, we may have to mess with it.
			//Safer than clone, which does a shallow copy, I believe.
			var query = json.fromJson(json.toJson(requestArgs.query));
			//Okay, object form query, we have to check to see if someone mixed query methods (such as using FilteringSelect
			//with a complexQuery).  In that case, the params need to be anded to the complex query statement.
			//See defect #7980
			if(typeof query == "object" ){
				var count = 0;
				var p;
				for(p in query){
					count++;
				}
				if(count > 1 && query.complexQuery){
					var cq = query.complexQuery;
					var wrapped = false;
					for(p in query){
						if(p !== "complexQuery"){
							//We should wrap this in () as it should and with the entire complex query
							//Not just part of it.
							if(!wrapped){
								cq = "( " + cq + " )";
								wrapped = true;
							}
							//Make sure strings are quoted when going into complexQuery merge.
							var v = requestArgs.query[p];
							if(lang.isString(v)){
								v = "'" + v + "'";
							}
							cq += " AND " + p + ":" + v;
							delete query[p];

						}
					}
					query.complexQuery = cq;
				}
			}

			var ignoreCase = requestArgs.queryOptions ? requestArgs.queryOptions.ignoreCase : false;
			//for complex queries only:  pattern = query[:|=]"NOT id:23* AND (type:'test*' OR dept:'bob') && !filed:true"
			//logical operators are case insensitive:  , NOT AND OR ( ) ! && ||  // "," included for quoted/string legacy queries.
			if(typeof query != "string"){
				query = json.toJson(query);
				query = query.replace(/\\\\/g,"\\"); //counter toJson expansion of backslashes, e.g., foo\\*bar test.
			}
			query = query.replace(/\\"/g,"\"");   //ditto, for embedded \" in lieu of " availability.
			var complexQuery = lang.trim(query.replace(/{|}/g,"")); //we can handle these, too.
			var pos2, i;
			if(complexQuery.match(/"? *complexQuery *"?:/)){ //case where widget required a json object, so use complexQuery:'the real query'
				complexQuery = lang.trim(complexQuery.replace(/"?\s*complexQuery\s*"?:/,""));
				var quotes = ["'",'"'];
				var pos1,colon;
				var flag = false;
				for(i = 0; i<quotes.length; i++){
					pos1 = complexQuery.indexOf(quotes[i]);
					pos2 = complexQuery.indexOf(quotes[i],1);
					colon = complexQuery.indexOf(":",1);
					if(pos1 === 0 && pos2 != -1 && colon < pos2){
						flag = true;
						break;
					} //first two sets of quotes don't occur before the first colon.
				}
				if(flag){	//dojo.toJson, and maybe user, adds surrounding quotes, which we need to remove.
					complexQuery = complexQuery.replace(/^\"|^\'|\"$|\'$/g,"");
				}
			} //end query="{complexQuery:'id:1* || dept:Sales'}" parsing (for when widget required json object query).
			var complexQuerySave = complexQuery;
			//valid logical operators.
			var begRegExp = /^>=|^<=|^<|^>|^,|^NOT |^AND |^OR |^\(|^\)|^!|^&&|^\|\|/i; //trailing space on some tokens on purpose.
			var sQuery = ""; //will be eval'ed for each i-th candidateItem, based on query components.
			var op = "";
			var val = "";
			var pos = -1;
			var err = false;
			var key = "";
			var value = "";
			var tok = "";
			pos2 = -1;
			for(i = 0; i < arrayOfItems.length; ++i){
				var match = true;
				var candidateItem = arrayOfItems[i];
				if(candidateItem === null){
					match = false;
				}else{
					//process entire string for this i-th candidateItem.
					complexQuery = complexQuerySave; //restore query for next candidateItem.
					sQuery = "";
					//work left to right, finding either key:value pair or logical operator at the beginning of the complexQuery string.
					//when found, concatenate to sQuery and remove from complexQuery and loop back.
					while(complexQuery.length > 0 && !err){
						op = complexQuery.match(begRegExp);

						//get/process/append one or two leading logical operators.
						while(op && !err){ //look for leading logical operators.
							complexQuery = lang.trim(complexQuery.replace(op[0],""));
							op = lang.trim(op[0]).toUpperCase();
							//convert some logical operators to their javascript equivalents for later eval.
							op = op == "NOT" ? "!" : op == "AND" || op == "," ? "&&" : op == "OR" ? "||" : op;
							op = " " + op + " ";
							sQuery += op;
							op = complexQuery.match(begRegExp);
						}//end op && !err

						//now get/process/append one key:value pair.
						if(complexQuery.length > 0){
							var opsRegex = /:|>=|<=|>|</g,
								matches = complexQuery.match(opsRegex),
								match = matches && matches.shift(),
								regex;

							pos = complexQuery.indexOf(match);
							if(pos == -1){
								err = true;
								break;
							}else{
								key = lang.trim(complexQuery.substring(0,pos).replace(/\"|\'/g,""));
								complexQuery = lang.trim(complexQuery.substring(pos + match.length));
								tok = complexQuery.match(/^\'|^\"/);	//quoted?
								if(tok){
									tok = tok[0];
									pos = complexQuery.indexOf(tok);
									pos2 = complexQuery.indexOf(tok,pos + 1);
									if(pos2 == -1){
										err = true;
										break;
									}
									value = complexQuery.substring(pos + match.length,pos2);
									if(pos2 == complexQuery.length - 1){ //quote is last character
										complexQuery = "";
									}else{
										complexQuery = lang.trim(complexQuery.substring(pos2 + 1));
									}
									if (match != ':') {
										regex = this.getValue(candidateItem, key) + match + value;
									} else {
										regex = filterUtil.patternToRegExp(value, ignoreCase);
									}
									sQuery += this._containsValue(candidateItem, key, value, regex);
								}
								else{ //not quoted, so a space, comma, or closing parens (or the end) will be the break.
									tok = complexQuery.match(/\s|\)|,/);
									if(tok){
										var pos3 = new Array(tok.length);
										for(var j = 0;j<tok.length;j++){
											pos3[j] = complexQuery.indexOf(tok[j]);
										}
										pos = pos3[0];
										if(pos3.length > 1){
											for(var j=1;j<pos3.length;j++){
												pos = Math.min(pos,pos3[j]);
											}
										}
										value = lang.trim(complexQuery.substring(0,pos));
										complexQuery = lang.trim(complexQuery.substring(pos));
									}else{ //not a space, so must be at the end of the complexQuery.
										value = lang.trim(complexQuery);
										complexQuery = "";
									} //end  inner if(tok) else
									if (match != ':') {
										regex = this.getValue(candidateItem, key) + match + value;
									} else {
										regex = filterUtil.patternToRegExp(value, ignoreCase);
										0 && console.log("regex value: ", value, " regex pattern: ", regex);
									}
									sQuery += this._containsValue(candidateItem, key, value, regex);
								} //end outer if(tok) else
							} //end found ":"
						} //end if(complexQuery.length > 0)
					} //end while complexQuery.length > 0 && !err, so finished the i-th item.
					match = eval(sQuery);
				} //end else is non-null candidateItem.
				if(match){
					items.push(candidateItem);
				}
			} //end for/next of all items.
			if(err){
				//soft fail.
				items = [];
				0 && console.log("The store's _fetchItems failed, probably due to a syntax error in query.");
			}
		}else{
			// No query...
			// We want a copy to pass back in case the parent wishes to sort the array.
			// We shouldn't allow resort of the internal list, so that multiple callers
			// can get lists and sort without affecting each other.  We also need to
			// filter out any null values that have been left as a result of deleteItem()
			// calls in ItemFileWriteStore.
			for(var i = 0; i < arrayOfItems.length; ++i){
				var item = arrayOfItems[i];
				if(item !== null){
					items.push(item);
				}
			}
		} //end if there is a query.
		findCallback(items, requestArgs);
	} //end filter function

});

});
