
// Ajax search forms.  depends on prototype
var AjaxSearchForm = Class.create();

// make sense of search response codes
AjaxSearchForm.responseCodeToString = function(code, count, max) {
    if (code == 2) {
	if (count && max) {
	    return "Your search returned " + count + " results.  The maximum number of results that can be displayed is " + max + ".  Please alter your search criteria and try again.";
	} else {
	    return "Your search returned too many results.  Please alter your search criteria and try again.";
	}
    } else if (code == 1) {
	return "Your search returned no results.  Please alter your search criteria and try again.";
    } else if (code == 3) {
	return "Your session has expired.  Please log in again.";
    } else {
	return "";
    }
}

AjaxSearchForm.prototype = {
    initialize: function() {
	this.minFieldsSet = 1;
	this.searchFields = {};
	this.searchOptions = {
	    minFieldsSet: 1  // minimum number of fields to require a value for before a search
	};
	this.outputElement = 'search_output'; // default element for output, set to whatever you want
	this.statusElement = 'search_status'; // default element for status, set to whatever you want
	this.searchSQLElement = 'search_sql'; // default element for params, set to whatever you want
	this.searchCriteriaElement = 'search_criteria'; // default element for params, set to whatever you want
	this.searchCountElement = 'search_count'; // default element for count, set to whatever you want
	this.showClassAfterCount = ''; // if you want to set up a class to show after a search, name it here
	this.showClassAfterSearch = ''; // if you want to set up a class to show after a search, name it here
    
	// default output function, called with results to populate from actual search
	this.outputFunction = function(element, results, status, criteria, sql) {
	    element.innerHTML = results.toString();
	};

	// post-output function which you can use to scroll, show/hide elements, etc.
	this.postOutputFunction = function(element, results, status, criteria, sql) {
	};
	
	// default function called when element count is received; hook to determine
	// whether to do search
	this.countReceivedFunction = function(count, status, criteria, sql) {
	    // if (status == '') {
		if (this.searchCountElement && $(this.searchCountElement))
		    $(this.searchCountElement).innerHTML = count;
		if (this.searchCriteriaElement && $(this.searchCriteriaElement))
		    $(this.searchCriteriaElement).innerHTML = criteria;
		if (this.searchSQLElement && $(this.searchSQLElement))
		    $(this.searchSQLElement).innerHTML = sql;
		if (this.statusElement && $(this.statusElement))
		    $(this.statusElement).innerHTML = AjaxSearchForm.responseCodeToString(status);
		if (this.showClassAfterCount && (this.showClassAfterCount != "")) {
		    var els = document.getElementsByClassName(this.showClassAfterCount);
		    els.each(Element.show);
		}   
	    // } else {
		//if (this.searchCountElement && $(this.searchCountElement))
		    //$(this.searchCountElement).innerHTML = "";
		//if (this.searchCriteriaElement && $(this.searchCriteriaElement))
		    //$(this.searchCriteriaElement).innerHTML = "";
		//if (this.searchSQLElement && $(this.searchSQLElement))
		    //$(this.searchSQLElement).innerHTML = "";
		//if (this.statusElement && $(this.statusElement))
		    //$(this.statusElement).innerHTML = AjaxSearchForm.responseCodeToString(status);
	    // }
	};
	// default status update function, called for each change in state with
	// an active request.  status here refers to the request status --
	// one of Uninitialized, Loading, Complete, or Failed
	this.stateChangeFunction = function(element, status) {
	    Logger.debug('changing state to ' + status);
	    if (status == 'Uninitialized') {
		$(element).innerHTML = "";
	    } else if (status == 'Loading') {
		$(element).innerHTML = "Loading...";
	    } else if (status == 'Complete') {
		$(element).innerHTML = "Complete.";
	    } else if (status == 'Failed') {
		$(element).innerHTML = "Failed.";
	    }
	};

	// max search limits
	this.maxResultCount = 500;
	this.maxReturnResults = 500;
	
	// search context to use...better set this
	this.searchContext = "";
	
	// for a search count:
	// things we want for a search count
	this.searchCountWants = "[ { js: 'count', key: 'SEARCHCOUNT', as: 'string' }, {js: 'status', key: 'RESPONSE_CODE', as: 'string'}, {js: 'criteria', key: 'SEARCHCRITERIA', as: 'string'}, {js: 'sql', key: 'SEARCHSQL', as: 'string'} ]";
	this.searchCountParameters = "SEARCHCOUNT=TRUE";

	// for a full search:
	// things we want for the actual search
	this.searchWants = "[ {js: 'results', key: 'RESULTS', as: 'array'}, {js: 'status', key: 'RESPONSE_CODE', as: 'string'}, {js: 'criteria', key: 'SEARCHCRITERIA', as: 'string'}, {js: 'sql', key: 'SEARCHSQL', as: 'string'} ]";
	this.searchParameters = "SEARCH=TRUE";
    },

    cleanup: function() {
	// drop references to external functions so that they can be GCed by
	// crappy browsers
	if (this.stateChangeFunction) 
	    this.stateChangeFunction = null;
	if (this.baseOutputFunction)
	    this.baseOutputFunction = null;
	if (this.outputFunction)
	    this.outputFunction = null;
	if (this.postOutputFunction)
	    this.postOutputFunction = null;
    },

    baseOutputFunction: function(element, results, status, criteria, sql) {
	// save the results then call the real output function
	this.lastResults = results;
	this.lastStatus = status;
	this.lastCriteria = criteria;
	this.lastSql = sql;
	var rv;
	if (this.outputFunction) {
	    rv = this.outputFunction(element, results, status, criteria, sql);
	}
	if (this.showClassAfterSearch && (this.showClassAfterSearch != "")) {
	    var els = document.getElementsByClassName(this.showClassAfterSearch);
	    els.each(Element.show);
	}
	return rv;
    },
    
    
    // default output function, called with results to populate from actual search
    outputFunction: function(element, results, status, criteria, sql) {
	element.innerHTML = results.toString();
    }

};

AjaxSearchForm.prototype.searchLimitsParams = function() {
    return "MAX_RESULT_COUNT=" + this.maxResultCount + "&MAX_RETURN_RESULTS=" + this.maxReturnResults; 
}

// set context
AjaxSearchForm.prototype.setContext = function(cd_Context) {
    this.searchContext = cd_Context;
}


// set max search limits
AjaxSearchForm.prototype.setMaxResultCount = function(max_count) {
	this.maxResultCount = max_count;
	this.maxReturnResults = max_count;
}

// add a form field to this search
AjaxSearchForm.prototype.addField = function(dom_id, tagName, doSearch, options) {
    if (doSearch == null) {
	doSearch = true;
    }
    if (options == null) {
	options = {
	    minSearchChars: 1   // require at least one char in a field before we consider it filled
	};
    }
    if (dom_id) {
	this.searchFields[dom_id] = { 'searchField': doSearch, 'tagName': tagName };
	$H(options).keys().each(function (k, i) {
	    this.searchFields[dom_id][k] = options[k];
	}.bind(this));
    }
};

// remove a form field from this search
AjaxSearchForm.prototype.removeField = function(dom_id) {
    if (this.searchFields[dom_id]) 
	delete this.searchFields[dom_id];
};

// internal: build a url query string out of our search fields
//cr012308 - due to comma to pipe delimter switch modified code to ensure pipe delimited multi-selects
AjaxSearchForm.prototype.buildRequestParameters = function() {
    var reqParms = new Array();
    $H(this.searchFields).keys().each(function (field, i) {
	var opts = this.searchFields[field];
	if (opts['searchField'] == true) {
	    if (opts['value']) {
		reqParms.push(encodeURIComponent(opts['tagName']) + "=" + encodeURIComponent(opts['value']));
	    } else {
		var field_val = $F(field);
		if (field_val && field_val != "") {
		    reqParms.push(encodeURIComponent(opts['tagName']) + "=" + encodeURIComponent(field_val.toString().replace(/,/g,"|")));
		}
	    }
	} 
    }.bind(this));
    if (reqParms.length >= this.searchOptions['minFieldsSet']) {
	// add search limits and context
	return "MS_CONTEXT=" + this.searchContext + "&" + reqParms.join("&") + "&" + this.searchLimitsParams();
    } else {
	return "";
    }
};

// set the output function, which does something with the returned data.
// It will be called as:
// outputFunction: function(element, results, status, criteria, sql) 
// status will be one of the mainstreet search statuses, results will
// be (in case of a successful search) an array of search results.
AjaxSearchForm.prototype.setOutputFunction = function(outputFunc) {
    this.outputFunction = outputFunc;
};

// post output function can do whatever you want.  I suggest scrolling to
// a spot, or showing/hiding elements, or what have you.
AjaxSearchForm.prototype.setPostOutputFunction = function(postOutputFunc) {
    this.postOutputFunction = postOutputFunc;
};

// Retrieve the results of the last search.  Returns an array
// [ results, status, criteria, sql ]
AjaxSearchForm.prototype.getLastResults = function() {
    return [ this.lastResults, this.lastStatus, this.lastCriteria, this.lastSql ];
}


// Do search with count and complete results if the result set is small enough.
AjaxSearchForm.prototype.doSearch = function() {
    if (this.searchContext == "") {
	Logger.error("no search context set, use setContext()");
	return;
    }

    var actuallySearchFunc = function() {
	Logger.info("search count is OK, performing search");
	this.doFullSearch();
    }.bind(this);
    
    this.doSearchCount(this.doFullSearch);
}

// get search count only.
AjaxSearchForm.prototype.doSearchCount = function(okToSearchCallback) {
    if (this.searchContext == "") {
	Logger.error("no search context set, use setContext()");
	return;
    }
    var parameters = this.buildRequestParameters();
    if (parameters != "") {
	// add WANTS for the count request
	parameters = parameters + "&WANTS=" + encodeURIComponent(this.searchCountWants);
	// and search count arguments
	parameters = parameters + "&" + this.searchCountParameters;
	Logger.warn(parameters);
	var asf = this;
	asf.req = new Ajax.Request(
	    "/ms/JSONQueryServlet",
	    {
		'method': 'post',
		'parameters': parameters,
		'onCreate': function(req) {
		    try {
			asf.stateChangeFunction(asf.statusElement, 'Uninitialized');
		    } catch (e) { }
		},
		'onLoading': function(req, responseHeader) {
		    try {
			asf.stateChangeFunction(asf.statusElement, Ajax.Request.Events[req.readyState]);
		    } catch (e) { }
		},
		'onComplete': function(req, responseHeader) {
		    Logger.debug("completed");
		    try {
			asf.stateChangeFunction(asf.statusElement, Ajax.Request.Events[req.readyState]);
		    } catch (e) {
			Logger.warn(e);	
		    }
		    var json = "var reply = " + req.responseText;
		    eval(json);
		    try {
			if (reply['count'] != "") {
			    if (reply['count'] <= asf.maxResultCount) {
				if (okToSearchCallback)
				    okToSearchCallback.call(asf);
			    } else if (reply['count'] == 0) {
				reply['status'] = 1;	
			    } else {
				reply['status'] = 2;
			    }
			}
			asf.countReceivedFunction(reply['count'], reply['status'], reply['criteria'], reply['sql']);
		    } catch(e) {
			Logger.warn(e);
		    }
		    asf.req = null;
		},
		'onFailure': function(req, responseHeader) {
		    asf.stateChangeFunction(asf.statusElement, 'Failure');
		    asf.req = null;
		}
	    }
	);
    }
};


// function to use for an event handler when you want to manually trigger
// the search (eg. a button).  Does not check count.
AjaxSearchForm.prototype.doFullSearch = function() {
    if (this.searchContext == "") {
	Logger.error("no search context set, use setContext()");
	return;
    }
    var parameters = this.buildRequestParameters();
    if (parameters != "") {
	// add WANTS for the full request
	parameters = parameters + "&WANTS=" + encodeURIComponent(this.searchWants);
	// and standard search arguments
	parameters = parameters + "&" + this.searchParameters;
	Logger.debug(parameters);
	var asf = this;
	asf.req = new Ajax.Request(
	    "/ms/JSONQueryServlet",
	    {
		'method': 'post',
		'parameters': parameters,
		'onCreate': function(req) {
		    try {
			asf.stateChangeFunction(asf.statusElement, 'Uninitialized');
		    } catch (e) { }
		},
		'onLoading': function(req, responseHeader) {
		    try {
			asf.stateChangeFunction(asf.statusElement, Ajax.Request.Events[req.readyState]);
		    } catch (e) { }
		},
		'onComplete': function(req, responseHeader) {
		    Logger.debug("completed");
		    try {
			asf.stateChangeFunction(asf.statusElement, Ajax.Request.Events[req.readyState]);
		    } catch (e) {
			Logger.warn(e);	
		    }
		    var json = "var reply = " + req.responseText;
		    eval(json);
		    try {
			asf.countReceivedFunction(reply['results'].length, reply['status'], reply['criteria'], reply['sql']);
			asf.baseOutputFunction(asf.outputElement, reply['results'], reply['status'], reply['criteria'], reply['sql']);
			asf.postOutputFunction(asf.outputElement, reply['results'], reply['status'], reply['criteria'], reply['sql']);
		    } catch(e) {
			Logger.warn(e);
		    }
		    asf.req = null;
		},
		'onFailure': function(req, responseHeader) {
		    asf.stateChangeFunction(asf.statusElement, 'Failure');
		    asf.req = null;
		}
	    }
	);
    }
};


