//
// Endeca Query Parser Class.
// This class wraps functionality to provide some level of "sane-ness" to the
// query-string syntax for posting requests to the Endeca server.
// The class takes some basic params, assumes defaults when necessary, and
// will generate a query string that can be sent to the Endeca server.
// It provides a "makeRequest()" function, which sends the request via Ajax
// and calls a callback function when complete.
// You can also use this class to re-query using strings received
// from a previous Endeca call (e.g. for paging, etc).
//
// IMPORTANT NOTE - This class does NOT (and should not) attempt to maintain
// state data about the page or the connection.  It's purpose is ONLY to
// generate a query and call a handler upon completion.  All other operations
// (e.g. "coremetrics", etc) should be handled by external objects.
//
// Use the EndecaCatalog and EndecaMeta classes to parse the response data.
//
// A very basic example, which creates a query for the word "lipstick", and
// uses the EndecaCatalog and EndecaMeta classes to parse the response into
// two global objects, and then calls "handleEndecaData()" when complete:
//
//  var eQuery = new EndecaQuery({
//		queryString: '',
//		searchTerm:  'lipstick',
//		filterProducts: true,
//		recsPerPage: 5,
//		callbackCompleted: function() {
//					g_eCatalog     = new EndecaCatalog({jsonResult: eQuery.jsonResult});
//					g_eCatalogMeta = new EndecaMeta({jsonResult: eQuery.jsonResult});
//					
//					handleEndecaData();
//			  	}		
//		});
//		
//	eQuery.makeRequest();


// Config params

// Port numbers of brand instances on the Endeca server:
var g_EndecaPort = {
	'CLUS': 15010,
	'ELUS': 15000
};
// Url for Endeca server.  'localhost' is relative to the apache server on the Endeca server.
var g_EndecaHost = 'localhost';
var g_EndecaUrl = '/enrpc/JSONControllerServlet.do';

// Brand/Endeca instance of Dimension id's we should ALWAYS include in "Ne" parms:
var g_EndecaNeIDs = {
	'CLUS': [8050,8051,8052,8053,8054,8061,8062,8063,8089,8095,8096,8127,8147]
};

var g_EndecaTypeAheadDimIds = {
	'CLUS': [8097]
};

var g_EndecaTypeAheadDimKeys = {
	'CLUS': ['TypeAheadSearch']
};

var g_EndecaLogging = {
	'CLUS': {
		'dev': {
			'host': 'localhost',
			'port': 15012
		},
		'www': {
			'host': 'njlndca01',
			'port': 15012
		}
	}
};


// Session id for this query
// CAUTION - basing this on "ngglobal" cookie, which is actually "generic"
// but may not apply to all brands.  Also, in a way, this is "dirty" cuz
// it's not purely related to Endeca, but for now it gets us there.
// We COULD make a unique id that we stick in this session...
var __eq_session_id = 0;
var getEQSessionId = function() {
	if ( !__eq_session_id ) {
		var cval = Cookie.get('ngglobal');
		if ( cval ) {
			__eq_session_id = cval;
		} else {
			__eq_session_id = Math.floor(Math.random()*999999)+1;
		}
	}
	return __eq_session_id;
}


var EndecaQuery = Class.create({

	initialize: function(args) {
		this.queryString = '';
		this.callbackCompleted  = function(){};
		this.rawResult = '';
		this.brand = 'CLUS';
		this.server = ( location.hostname.indexOf('dev') >= 0 ? 'dev' : 'www' );
		// The N record can be a number, or zero (for root), or a list (array)
		this.NRecordId = 0;
		this.NeRecordId = '';
		this.NaoRecordId = 0;
		this.rollupId = 'p_PRODUCT_ID';
		this.rollupProducts = true;
		this.rollupDetail = true;
		this.computePhrasings = true;
		this.searchTerm = '';
		this.searchMode = '';
		this.searchMatchMode = 'matchallpartial'; // default
		//this.searchMatchMode = 'matchallany'; // default
		this.searchKey = '';
		this.didYouMean = 1;
		this.sortKey = '';
		this.sortAsc = true;
		this.rangeFilter = '';
		this.recordFilter = '';
		this.recsPerPage = 10;
		this.sessionId = getEQSessionId();
		this.enableLogging = true;
		
		// Use this to request one specific exact record, using "rec_id" property.
		// IMPORTANT: This will effectively override all other params!
		this.recordSpecId = '';
		
		this.searchDimensions = true;
		
		// This flag, if true, triggers an abbreviated (quick) search for type-ahead
		this.typeAheadSearch = false;
		
		// Used as Nf if rangeFilter is blank:
		this.filterShoppable = true;
		this.filterPromotional = false;
		this.filterDisplayable = true;
		this.filterSearchable = false; //true;
		
		// Used for Nr (record filter) if recordFilter is blank.
		// Generally you'll only search for one of these
		// (else leave off to search for all types)
		this.filterProducts = false;
		this.filterContent = false;
		
		// Load up passed params
        Object.extend(this, args || {});
	},
	
	// Given a query string, parse it into our internal data.
	// When in doubt, use some rational default.
	parseQueryString: function(qs) {
		if ( qs ) {
			var qparms = $H(qs.toQueryParams());
			var val,arv;
			val = qparms.get('N');
			this.NRecordId = ( val == undefined ? 0 : val.split('+') );
			val = qparms.get('Ne');
			this.NeRecordId = ( val == undefined ? '' : val.split('+') );
			val = qparms.get('Nu');
			this.rollupId = ( val == undefined ? 'p_PRODUCT_ID' : val );
			val = qparms.get('Nao');
			this.NaoRecordId = ( val == undefined ? 0 : val );
			val = qparms.get('Np');
			this.rollupDetail = ( val == undefined ? 2 : val );
			val = qparms.get('Ntt');
			this.searchTerm = ( val == undefined ? '' : unescape(val) );
			val = qparms.get('Ntk');
			this.searchKey = ( val == undefined ? '' : val );
			val = qparms.get('Ntx');
			this.searchMode = ( val == undefined ? '' : val );
			val = qparms.get('Nty');
			this.didYouMean = ( val == undefined ? 0 : parseInt(val) ? 1 : 0 );
			val = qparms.get('Nf');
			this.rangeFilter = ( val == undefined ? '' : val );
			val = qparms.get('Nr');
			this.recordFilter = ( val == undefined ? '' : val );
			val = qparms.get('Ns');
			arv = ( val ? val.split('|') : [] );
			this.sortKey = ( val && arv[0] ? arv[0] : '' );
			this.sortAsc = ( arv[1] ? false : true );
			val = qparms.get('R');
			this.recordSpecId = ( val == undefined ? '' : val );
		}
	},

	// Build the query string to send to Endeca	
	buildQueryString: function(qs) {
		
		// Check for given query string
		if ( this.queryString && !qs ) {
			qs = this.queryString;
		}
		
		// Parse out query string if we have it.
		if ( qs ) {
			this.parseQueryString(qs);
		}
		
		// Clean up search term a little
		this.searchTerm = String(this.searchTerm).strip();
		
		// Build the array of parms we'll use for the new query string
		var arQS = [
			this.getM(),
			this.getL(),
			this.getN(),
			this.getNe(),
			this.getNao(),
			this.getNu(),
			this.getNp(),
			this.getNtt(),
			this.getNtk(),
			this.getNtx(),
			this.getNty(),
			this.getNf(),
			this.getNr(),
			this.getNs(),
			this.getNtpc(),
			this.getNtpr(),
			this.getD(),
			this.getDx(),
			this.getDi(),
			this.getR()
		];
		
		// Filter out empty parms
		arQS = arQS.findAll(function(el){return el.length > 0;});
		
		// Make the query string
		this.queryString = arQS.join('&');
		
		return this.queryString;
	},
	
	makeRequest: function(qs) {
		qs = this.buildQueryString(qs);
		var url = g_EndecaUrl + '?' + qs;
		new Ajax.Request ( url, {
	  		method: 'get',
	  		onComplete: this.onComplete.bind(this)
	  	});
	},
	
	onComplete: function(t) {
		this.rawResult = t.responseText;
		this.jsonResult = this.rawResult.evalJSON();
		this.callbackCompleted(this);
	},
	
	getM: function() {
		var mStr = '';
		
		// If we know the port, set it here
		var port = g_EndecaPort[this.brand];
		if ( port ) {
			mStr += 'host:' + g_EndecaHost + '|' + 'port:' + port;
		}
		
		// If recsPerPage is not the Endeca "default" value, set it here.
		if ( this.recsPerPage != 10 ) {
			if ( mStr.length > 0 ) mStr += '|';
			mStr += 'recs_per_page:' + this.recsPerPage;
		}
		
		if ( mStr.length > 0 ) {
			mStr = 'M=' + mStr;
		}
		
		return mStr;
	},
	
	getL: function() {
		var mStr = '';
		
		if ( this.enableLogging &&
			 g_EndecaLogging[this.brand] &&
			 g_EndecaLogging[this.brand][this.server] ) {
			var host = g_EndecaLogging[this.brand][this.server]['host'];
			var port = g_EndecaLogging[this.brand][this.server]['port'];
			
			mStr = 'L=SESSION_ID:'+this.sessionId+'|host:'+host+'|port:'+port;
		}
		
		return mStr;
	},
	
	getN: function() {
		// The N record can be a number, or zero (for root), or a list
		if ( this.recordSpecId ) return '';
		var val = '';
		if ( typeof this.NRecordId != "undefined" ) {
			val = $A(this.NRecordId).join('+');
		}
		return 'N='+val;
		//return ( this.searchTerm.blank() ? 'N=0' : 'N=' );
	},
	
	// Ne - ID's of dimensions we want to return values for by default.
	// These ID's are generated by Endeca when dimensions are created, so they
	// will be unique per brand/endeca instance.
	getNe: function() {
		if ( this.recordSpecId ) return '';
		var ar = g_EndecaNeIDs[this.brand];
		if ( typeof this.NeRecordId != "undefined" ) {
			ar = ar.concat($A(this.NeRecordId));
		}
		var str = ( ar ? 'Ne='+ar.join('+') : '' );
		return str;
	},
	
	getNao: function() {
		// This is the page record id.
		if ( this.recordSpecId ) return '';
		var val = ( this.NaoRecordId ? this.NaoRecordId : 0 );
		return 'Nao='+val;
	},
	
	// Nu - rollup id to use.
	getNu: function() {
		if ( this.recordSpecId ) return '';
		var val = ( this.rollupId ? this.rollupId : 'p_PRODUCT_ID' );
		return ( this.rollupProducts ? 'Nu='+val : '' );
	},
	
	// Np - type of rollup.  1 = summary only, 2 = all records
	getNp: function() {
		if ( this.recordSpecId ) return '';
		var val = this.rollupDetail ? 2 : 1;
		return ( this.rollupProducts ? 'Np='+val : '' );
	},
	
	// Ntt - search term. This is the string to search for.
	getNtt: function() {
		if ( this.recordSpecId ) return '';
		//return ( this.searchTerm.blank() || this.typeAheadSearch ? '' : 'Ntt='+escape(this.searchTerm) );
		return ( this.searchTerm.blank() ? '' : 'Ntt='+escape(this.searchTerm) );
	},
	
	// Ntk - search key.  Usually "all", but can be one or more of pre-defined search keys
	// (e.g. "DESCRIPTION", "PRODUCT_NAME", etc).  Only return a value if we have a search term too.
	getNtk: function() {
		if ( this.recordSpecId ) return '';
		var str = '';
		
		//if ( !this.searchTerm.blank() && !this.typeAheadSearch ) {
		if ( !this.searchTerm.blank() ) {
			if ( this.searchKey.blank() ) {
				this.searchKey = 'all';
			}
			var skey = this.searchKey;
			
			if ( this.typeAheadSearch ) {
				var ar = g_EndecaTypeAheadDimKeys[this.brand];
				if ( ar ) {
					skey = ar.join('+');
				}
			}
			
			str = 'Ntk='+skey;
		}
		
		return str;
	},
	
	// Ntx - search mode.  This can get interesting, but for now we'll stick with all>partial.
	getNtx: function() {
		if ( this.recordSpecId ) return '';
		var str = '';

		//if ( !this.searchTerm.blank() && !this.typeAheadSearch ) {
		if ( !this.searchTerm.blank() ) {
			if ( this.searchMode.blank() ) {
				this.searchMode = 'mode+' + this.searchMatchMode;
			}
			str = 'Ntx='+this.searchMode;
		}

		return str;
	},
	
	// Nty - "did you mean" setting. 0=off, 1=on.  Only relevant when searching.
	getNty: function() {
		if ( this.recordSpecId ) return '';
		var str = '';

		if ( !this.searchTerm.blank() && this.didYouMean ) {
			str = 'Nty=1';
		}
		
		return str;
	},
	
	// Nf - range filter
	getNf: function() {
		if ( this.recordSpecId ) return '';
		var str = '';
		var arFilters = [];
		
		if ( this.rangeFilter.blank() ) {
			if ( this.filterShoppable   ) arFilters.push('s_shoppable|GT+0');
			if ( this.filterPromotional ) arFilters.push('s_promotional|GT+0');
			if ( this.filterDisplayable ) arFilters.push('s_displayable|GT+0');
			if ( this.filterSearchable  ) arFilters.push('s_searchable|GT+0');
			if ( arFilters.length > 0 )
				str = 'Nf='+arFilters.join('|');
		} else {
			// rangeFilter must be set to some string - just use it.
			str = 'Nf=' + this.rangeFilter;
		}
		
		return str;
	},
	
	// Nr - record filter
	getNr: function() {
		if ( this.recordSpecId ) return '';
		var str = '';
		var arFilters = [];
		
		if ( this.recordFilter.blank() ) {
			if ( this.filterProducts ) arFilters.push('rec_type:product');
			if ( this.filterContent  ) arFilters.push('rec_type:content');
			if ( arFilters.length > 0 )
				str = 'Nr=AND(' + arFilters.join(',') + ')';
		} else {
			str = 'Nr=' + this.recordFilter;
		}
		
		return str;
	},
	
	getNs: function() {
		if ( this.recordSpecId ) return '';
		var str = '';
		if ( this.sortKey ) {
			str = 'Ns=' + this.sortKey;
			if ( !this.sortAsc ) {
				str += '|1';
			}
		}
		return str;
	},
	
	getNtpc: function() {
		if ( this.recordSpecId ) return '';
		var str = ( this.computePhrasings && !this.searchTerm.blank() ? 'Ntpc=1' : '' );
		return str;
	},

	getNtpr: function() {
		if ( this.recordSpecId ) return '';
		var str = ( this.computePhrasings && !this.searchTerm.blank() ? 'Ntpr=1' : '' );
		return str;
	},
	
	// D and Dx are dimension search analogues to Ntt and Ntx.
	getD: function() {
		if ( this.recordSpecId ) return '';
		var str = '';
		
		if ( this.searchDimensions && !this.searchTerm.blank() ) {
			str = 'D='+escape(this.searchTerm);
		}
		
		return str;
	},
	
	getDx: function() {
		if ( this.recordSpecId ) return '';
		var str = '';

		if ( this.searchDimensions && !this.searchTerm.blank() ) {
			if ( this.searchMode.blank() ) {
				this.searchMode = 'mode+' + this.searchMatchMode;
			}
			str = 'Dx='+this.searchMode;
		}

		return str;
	},
	
	getDi: function() {
		if ( this.recordSpecId ) return '';
		var str = '';
		
		// Don't bother unless this is a typeahead search
		if ( this.searchDimensions && !this.searchTerm.blank() && this.typeAheadSearch ) {
			var ar = g_EndecaTypeAheadDimIds[this.brand];
			if ( ar ) {
				str = 'Di='+ar.join('+');
			}
		}
		
		return str;
	},
	
	getR: function() {
		var str = '';
		
		if ( this.recordSpecId ) {
			str = 'R=' + this.recordSpecId;
		}
		
		return str;
	}
	
});

/*
thoughts on data:

should have a catprodsku.js file that has classes for:
	ECategory
	EProduct
	ESku
	ECategoryList
	EProductList
	ESkuList
	ECatalog
	
Catalog is entry point.  Maintains *List objects.
Can parse current prodcat json or endeca.

ECatalog.addRecord()
 - takes one record of "Properties" hash in result set
 - Calls ECategoryList.addRecord()
 	- Looks up existing ECategory object or creates new one.
 	- Pulls out all "c_" fields
 	- Calls EProduct.addRecord()
 		- Pulls out all "p_" fields
 		- Calls ESku.addRecord()
 			- Pulls out all "s_"fields

hmm... rollup on p_path instead of p_PRODUCT_ID?

Data Hash Format:

	AggrRecords: [
		{	-- new product
			Records: [
				{
					Properties: {
						c_*
						p_*
						s_*
					}
				}
				{
					Properties: {
						c_*
						p_*
						s_*
					}
				}
			]
		}
		{	-- new product
			Records: [
				{
					Properties: {
						c_*
						p_*
						s_*
					}
				}
				{
					Properties: {
						c_*
						p_*
						s_*
					}
				}
			]
		}
	]
	
resultData = $H(results);
resultData['AggrRecords'].each(function(prodHash) {
	prodHash['Records'].each(function(recHash) {
		var propHash = recHash['Properties'];
		var catProps = propHash.findAll(function(el){return el.key
	});
});

*/
