/*
Package: manager.js

Namespace: bbop.golr.manager

Generic BBOP manager for dealing with gross GOlr configuration and
management. Remember, this is actually a "subclass" of
<bbop.registry>. The defined events for this registry are: "reset",
"search", and "error".

reset - functions for initializing and resetting
search - functions for receiving standard search results
error - functions to call when something goes very wrong

Both <bbop.golr.response> (or clean error data) and the manager
itself (this as anchor) should be passed to the callbacks.

TODO/BUG: <set_query> and <set_default_query> should both take
strings or <bbop.logic> as arguments. Those, as well as <get_query>
and <get_query> should only return <bbop.logic>.
 /*
Constructor: manager

Contructor for the GOlr query manager

Arguments:
golr_loc - string url to GOlr server;
golr_conf_obj - a <bbop.golr.conf> object

Returns:
golr manager object

See also:
<bbop.registry>

A manager for handling the AJAX and registry. Initial take from
{module:bbop-rest-manager}.

@constructor
@param {String} barista_location - string for invariant part of API
@param {String} namespace - string for namespace of API to use
@param {String} user_token - identifying string for the user of the manager (Barista token)
@param {Object} engine - Remote resource manager client to use (must be an instantiated {module:bbop-rest-manager} engine)
@param {String} mode - whether or not to have utility methods (most besides fetch_with and start_with be in); options are "sync" and "async", which correspond to internally using fetch and start respectively
@returns {manager} a classic manager
function (golr_loc, golr_conf_obj, engine, mode){
    registry.call(this, ['prerun',
			 'reset',
			 'search',
			 'error',
			 'postrun']);
    this._is_a = 'bbop-manager-golr';
    var anchor = this; // get a good self-reference point

    // Per-manager logger.
    this._logger = new bbop.logger(this._is_a);
    //this._logger.DEBUG = true;
    this._logger.DEBUG = false;
    function ll(str){ anchor._logger.kvetch(str); }

    // Setup right handler subsystem--either returning a direct
    // response or a promise, depending.
    anchor._engine = engine || null;
    anchor._mode = mode || null;
    anchor._runner = function(resource, payload){
	var ret = null;
	// if( typeof(anchor._engine) === 'undefined' ){
	//     throw new Error('"engine" not set in new bbop-manager-golr');
	// }
	if( anchor._mode === 'sync' ){
	    ret = anchor._engine.fetch(resource, payload);
	}else if( anchor._mode === 'async' ){
	    ret = anchor._engine.start(resource, payload);
	}else{
	    // throw new Error('"mode" not set in new bbop-manager-golr');
	    return resource;
	}
	return ret;
    };

    // The callback function called after a successful AJAX
    // intialization/reset call. First it runs some template code,
    // then it does all of the callbacks.
    anchor._run_reset_callbacks = function(response){
	ll('run reset callbacks...');
	//var response = new golr_response(json_data);
	anchor.apply_callbacks('reset', [response, anchor]);
	anchor.apply_callbacks('postrun', [response, anchor]);
    };

    // The main callback function called after a successful AJAX call in
    // the update function.
    anchor._run_search_callbacks = function(response){
	ll('run search callbacks...');
	//var response = new golr_response(json_data);
	anchor.apply_callbacks('search', [response, anchor]);
	anchor.apply_callbacks('postrun', [response, anchor]);
    };

    // This set is called when we run into a problem.
    anchor._run_error_callbacks = function(response){
	ll('run error callbacks...');
	//var response = new golr_response(json_data);
	anchor.apply_callbacks('error', [response, anchor]);
	anchor.apply_callbacks('postrun', [response, anchor]);
    };

    // The registries can only be applied if the engine is present.
    if( engine ){

	// Re-contextualize run_promise_functions().
	anchor.run_promise_functions = function(promise_function_stack, 
						accumulator_function,
						final_function,
						error_function){
	    // NOTE: Cannot sensibly use Function.call here as engine
	    // is not really the superclass of this manager.
	    return engine.run_promise_functions(promise_function_stack,
						accumulator_function,
						final_function,
						error_function);
	};

	// Setup the appropriate callbacks in the engine for success.
	anchor._engine.register('success', function(response){
	    
    	    //console.log(anchor._engine);
    	    //console.log(response._raw);
	    
            // 
            if( ! response.success() ){
		throw new Error("Unsuccessful response from golr server!");
            }else{
		var cb_type = response.callback_type();
		//console.log('okay response from server, will probe type...: '+cb_type);
		if( cb_type === 'reset' ){
                    anchor._run_reset_callbacks(response);
		}else if( cb_type === 'search' ){
                    anchor._run_search_callbacks(response);
		}else{
                    throw new Error("Unknown callback type (runner): "+cb_type);
		}
            }
	});
	
	// And, more easily, setup the appropriate callbacks in the
	// engine for failure.
	anchor._engine.register('error', function(response){
    	    anchor._run_error_callbacks(response);
	});
	
    }

    // Just want to compile these once.
    var alphanum = new RegExp(/^[a-zA-Z0-9 ]+$/);

    this.minimal_query_length = 3;

    // To help keep requests from the past haunting us. Actually doing
    // something with this number is up to the UI.
    this.last_sent_packet = 0;
    //this.last_received_packet = 0;

    // Lightly check incoming arguments.
    // There should be a string url argument.
    // There could be a hash of pinned filters argument.
    if( ! golr_loc || ! golr_conf_obj ){
	ll('ERROR: no proper arguments');
    }
    if( typeof golr_loc !== 'string' ){
	ll('ERROR: no proper golr url string argument');
    }
    if(	! golr_conf_obj._is_a || golr_conf_obj._is_a !== 'golr-conf.conf' ){
	    ll('ERROR: no proper golr-conf.conf object argument');
	    throw new Error('boink! ' + bbop.what_is(golr_conf_obj) );
	}
    
    // Whether or not to prevent ajax events from going.
    // This may not be usable, or applicable, to all backends.
    this._safety = false;

    // Our default target url.
    this._solr_url = golr_loc;

    // Settle in the configurations.
    // this._golr_conf = new bbop_golr.conf(golr_conf_var);
    this._golr_conf = golr_conf_obj;

    // The current data batches that we are storing.
    this._batch_urls = [];
    this._batch_accumulator_func = function(){};
    this._batch_final_func = function(){};

    // The current state stack.
    this._excursions = [];

    // The current class/personality that we're using. It may be none.
    this._current_class = null;

    // Our (default) query and the real deal.
    this.fundamental_query = '*:*'; // cannot be changed
    this.default_query = '*:*'; // changable
    this.query = this.default_query; // current

    // Our (default) fl and whatever we have now.
    //this.default_fl = '*%2Cscore';
    this.default_fl = '*,score';
    this.current_fl = this.default_fl;

    // We remember defaults in the case of rows and start since they
    // are the core to any paging mechanisms and may change often.
    //this.default_rows = 25;
    //this.default_rows = 100;
    this.default_rows = 10;
    this.default_start = 0;
    this.current_rows = this.default_rows;
    this.current_start = this.default_start;

    // There is a reason for this...TODO: later (25+)
    this.default_facet_limit = 25;
    this.current_facet_limit = 25;
    // {facet_field_name: value, ...}
    this.current_facet_field_limits = {};
    // TODO: paging for facets;
    this.current_facet_offset = 25;
    this.current_facet_field_offsets = {};

    // Number of snippets to return when hl is enable
    this.default_hl_snippets = 1000;

    // Our default query args, with facet fields plugged in.
    this.query_variants =
	{
	    // Our default standard search type. This means we don't
	    // have to explicitly add fields to the search (although
	    // the query fields ('qf') are still necessary to make
	    // anything real happen).
	    defType: 'edismax',
	    
	    // Things unlikely to be touched.
	    // There are unlikely to be messed with too much.
	    qt: 'standard',
	    indent: 'on',
	    wt: 'json',
	    //version: '2.2',
	    rows: anchor.current_rows,
	    start: anchor.current_start, // Solr is offset indexing
	    //fl: '*%2Cscore',
	    fl: anchor.default_fl,
    
	    // Deprecated: see query_filters
	    //fq: {},
	    
	    // Deprecated: see query_fields
	    //qf: {},
	    
	    // Deprecated: see query
	    //q: '*:*'

	    // Control of facets.
	    facet: 'true',
	    'facet.mincount': 1,
	    'facet.sort': 'count',
	    'json.nl': 'arrarr', // only in facets right now
	    'facet.limit': anchor.default_facet_limit
	    // TODO?: 'f.???.facet.limit': 50,
	    // TODO: 'json.nl': [flat|map|arrarr]

	    // Deprecated: see facet_fields
	    //'facet.field': []
	};

    // This is the 'qf' parameter. Although we keep it, it only needs
    // to be exposed when the query ('q') field is set.
    //this.query_fields = [];
    this.query_fields = {};

    // A richer way to handle the 'fq' query variant.
    // It should look like:
    // {<filter>: {<value>:{'sticky_p':(t|f), 'negative_p':(t|f)}, ...}}
    this.query_filters = {};

    // The engine for the facet.field list.
    this.facet_fields = {};

    /*
     * Function: debug
     * 
     * Turn on or off the verbose messages. Uses <bbop.logger>, so
     * they should come out everywhere.
     * 
     * Parameters: 
     *  p - *[optional]* true or false for debugging
     *
     * Returns: 
     *  boolean; the current state of debugging
     */
    this.debug = function(p){
	if( p === true || p === false ){
	    this._logger.DEBUG = p;
	    // TODO: add debug parameter a la include_highlighting
	}
	return this._logger.DEBUG;
    };

    /*
     * Function: minimal_query
     * 
     * The length of the minimal query to consider. Defaults to 3.
     * 
     * Parameters: 
     *  n - *[optional]* number, or nothing if just want current
     *
     * Returns: 
     *  number
     */
    this.minimal_query = function(n){
	if( us.isNumber(n) ){
	    anchor.minimal_query_length = n;
	}
	return this.minimal_query_length;
    };

    /*
     * Function: lite
     * 
     * Limit the returns fields (the parameter "fl") to the ones
     * defined in the set of fields defined in results, label fields
     * if available (i.e. "_label", "_map" when "_label" is
     * multi=valued), and "score" and "id".
     * 
     * The default is "false".
     * 
     * Parameters: 
     *  use_lite_p - *[optional]* true or false, none just returns current
     *
     * Returns: 
     *  boolean; the current state of lite-ness
     */
    this.lite = function(use_lite_p){

	// Adjust the current state accordingly.
	if( use_lite_p === true || use_lite_p === false ){
	    if( use_lite_p === true ){
		
		// The actual collections and adjustment.
		// First, this only works if we have a personality, so
		// check to see if we have one.
		var per = anchor.get_personality();
		if( per ){
		    // Since we have a personality, collect all of the
		    // mentioned fields.
		    var field_collection = {};
		    var union = bbop.merge;
		    var ccl = anchor._current_class;

		    // Fill field_collection with the fields
		    // in the given category.
		    //loop(['boost', 'result', 'filter'],
		    //loop(['result', 'filter'],
		    each(['result'], function(cat){
			field_collection = 
			    union(field_collection, ccl.get_weights(cat));
		    });
		    
		    // Next, flatten into a list.
		    var flist = us.keys(field_collection);

		    // Now for all the fields in these categories, see
		    // if we can find additional "special" labels to
		    // go with them.
		    each(flist, function(flist_item){
			//loop(['_label', '_label_searchable'],
			each(['_label'], function(field_suffix){
			    var new_field = flist_item + field_suffix;
			    var nf_obj = ccl.get_field(new_field);
			    if( nf_obj ){
				flist.push(new_field);
				
				// There appears to be the
				// thing label. If they are
				// both multi-valued, then
				// there will be a map as
				// well.
				if( nf_obj.is_multi() ){
				    flist.push(flist_item + '_map');
				}
			    }
			});
		    });
		    
		    // Finally, set these fields (plus score) as the
		    // new return fields.
		    flist.push('score');
		    flist.push('id');
		    //anchor.current_fl = flist.join('%2C');
		    anchor.current_fl = flist.join(',');
		    anchor.set('fl', anchor.current_fl);
		}
		
	    }else{ // else false
		// Reset.
		anchor.current_fl = anchor.default_fl;
		anchor.set('fl', anchor.current_fl);
	    }
	}

	// Return the current state.
	var retval = false;
	if( anchor.default_fl !== anchor.current_fl ){
	    retval = true;
	}
	return retval;
    };

    // An internal helper function to munge the name of a field into
    // the name of its corresponding facet field.
    function _field_to_facet_field(field){
	return 'f.' + field + '.facet.limit';
    }
    
    /*
     * Function: get_facet_limit
     * 
     * Get the limit for a specified facet or the global limit.
     * 
     * Parameters: 
     *  field - *[optional]* limit for a specific field; otherwise global value
     *
     * Returns: 
     *  integer or null
     */
    this.get_facet_limit = function(field){
	var retval = null;

	if( ! field ){
	    retval = anchor.current_facet_limit;
	}else{
	    var f = _field_to_facet_field(field);
	    var try_val = anchor.current_facet_field_limits[f];
	    if( typeof(try_val) !== 'undefined' ){
		retval = try_val;
	    }
	}

	return retval;
    };

    /*
     * Function: set_facet_limit
     * 
     * Change the number of facet values returned per call.
     * The default is likely 25.
     * 
     * Just as in Solr, a -1 argument is how to indicate unlimted
     * facet returns.
     * 
     * This setting does not survive things like <resets_facet_limit>.
     * 
     * Parameters: 
     *  arg1 - (integer) set the global limit
     *
     * Parameters: 
     *  arg1 - (string) the name of the field to check
     *  arg2 - (integer) set the limit for this field
     *
     * Returns: 
     *  boolean on whether something was set
     */
    this.set_facet_limit = function(arg1, arg2){
	var retval = false;

	// Decide which form of the function we're using.
	if( typeof(arg2) === 'undefined' &&
	    bbop.what_is(arg1) === 'number' ){ // form one
		
		// Set
		var nlimit = arg1;
		anchor.current_facet_limit = nlimit;
		anchor.set('facet.limit', anchor.current_facet_limit);
		
		retval = true;
	
	}else if( typeof(arg1) !== 'undefined' && 
		  typeof(arg2) !== 'undefined' && 
		  bbop.what_is(arg1) === 'string' &&
		  bbop.what_is(arg2) === 'number' ){
		      
		      var field = _field_to_facet_field(arg1);
		      var limit = arg2;
		      anchor.current_facet_field_limits[field] = limit;
		      
		      retval = true;
	}

	return retval;
    };

    /*
     * Function: set_default_facet_limit
     * 
     * Permanently change the default number of facet values returned
     * per call. The default's default is likely 25.
     * 
     * Just as in Solr, a -1 argument is how to indicate unlimted
     * facet returns.
     * 
     * Parameters: 
     *  lim - (integer) set the global default limit
     *
     * Returns: 
     *  old default
     */
    this.set_default_facet_limit = function(lim){

	// Capture ret.
	var retval = anchor.default_facet_limit;

	// Set
	anchor.default_facet_limit = lim;
	//anchor.set('facet.limit', anchor.default_facet_limit);
		
	return retval;
    };

    /*
     * Function: reset_facet_limit
     * 
     * Either reset the global limit to the original (likely 25)
     * and/or remove the specified filter. Sets everything back to the
     * original values or whatever was set by
     * <set_default_facet_limit>.
     * 
     * Parameters: 
     *  field - *[optional]* remove limit for a field; otherwise all and global
     *
     * Returns: 
     *  boolean on whether something was reset
     */
    this.reset_facet_limit = function(field){
	var retval = false;

	if( typeof(field) === 'undefined' ){
	    // Eliminate all fields by blowing them away.
	    anchor.current_facet_limit = anchor.default_facet_limit;
	    anchor.set('facet.limit', anchor.current_facet_limit);
	    anchor.current_facet_field_limits = {};
	    retval = true;
	}else{ // eliminate just the one field
	    var f = _field_to_facet_field(field);
	    if( typeof(anchor.current_facet_field_limits[f]) !== 'undefined' ){
		delete anchor.current_facet_field_limits[f];
		retval = true;
	    }
	}

	return retval;
    };

    /*
     * Function: get_results_count
     * 
     * Get the current number of results that will be returned.
     * 
     * Parameters: 
     *  n/a
     *
     * Returns: 
     *  integer
     */
    this.get_results_count = function(field){
	return anchor.get('rows');
    };

    /*
     * Function: set_results_count
     * 
     * Change the number of result documents returned per call.
     * The default is likely 10.
     * 
     * Parameters: 
     *  count - (integer) set the global results count
     *
     * Returns:
     *  the count set
     */
    this.set_results_count = function(count){
	anchor.set('rows', count);
	anchor.current_rows = count;
	return anchor.current_rows;
    };

    /*
     * Function: reset_results_count
     * 
     * Reset the number of documents to their original setting, likely
     * 10.
     * 
     * Parameters: 
     *  n/a
     *
     * Returns:
     *  the new count
     */
    this.reset_results_count = function(){
	anchor.set('rows', anchor.default_rows);
	anchor.current_rows = anchor.default_rows;
	return anchor.current_rows;
    };

    /*
     * Function: plist_to_property_hash
     *
     * Turn a plist to a hash containing the different properties that
     * can be defined for a query filter. Possible values are: '+'
     * (positive filter), '-' (negative filter), '*' (sticky filter),
     * '$' (transient). If mutually exclusive properties are defined
     * (e.g. both '+' and '-'), the last one will be used. Or, since
     * that is a call to silliness, let's say the behavior is
     * undefined.
     *
     * Parameters: 
     *  plist - *[optional]* a list of properties to apply to the filter
     *
     * Returns: 
     *  A hash version of the plist; otherwise, the default property hash
     */
    this.plist_to_property_hash = function(plist){

	// Let's start with the default values.
	var phash = {
	    //'positive_p': true,
	    'negative_p': false,
	    //'transient_p': true
	    'sticky_p': false
	};

	// If not defined, just return the default list.
	if( plist ){	    
	    each(plist, function(item){
		if( item === '+' ){
		    phash['negative_p'] = false;
		    //phash['positive_p'] = true;
		}else if( item === '-' ){
		    phash['negative_p'] = true;
		    //phash['positive_p'] = false;
		}else if( item === '*' ){
		    phash['sticky_p'] = true;
		    //phash['transient_p'] = false;
		}else if( item === '$' ){
		    phash['sticky_p'] = false;
		    //phash['transient_p'] = true;
		}
	    });
	}

	return phash;
    };

    /*
     * Function: add_query_filter_as_string
     *
     * Setter for query filters ('fq'). Acts as a 
     *
     * Parameters: 
     *  filter_string - filter (type) string (e.g. "-type:gene")
     *  plist - *[optional]* list of properties of the filter
     *
     * Returns: 
     *  (TODO) The current query filter hash.
     * 
     * See also:
     *  <add_query_filter>
     */
    this.add_query_filter_as_string = function(filter_string, plist){
	
	// Split the incoming filter string into its component parts.
	var f_v = bbop.first_split(':', filter_string);
	var fname = f_v[0];
	var fval = f_v[1];

	// Need to shuck the value from the quotes, as in load_url.
	fval = bbop.dequote(fval);

	var props = null;
	if( us.isArray(plist) ){
	    props = plist;
	}else{
	    props = [];
	}

	// Only continue on sensible inputs.
	var ret = {};
	if( fname !== '' && fval !== '' ){

	    // Similar to the URL loader.
	    var lead_char = fname.charAt(0);
	    if( lead_char === '-' || lead_char === '+' ){
		props.push(lead_char);
		fname = fname.substr(1, fname.length -1);
	    }
	    
	    ret = this.add_query_filter(fname, fval, props);
	}

	return ret;
    };

    /*
     * Function: add_query_filter
     *
     * Setter for query filters ('fq').
     *
     * Parameters: 
     *  filter - filter (type) string
     *  value - filter value string (or TODO: defined logic hash)
     *  plist - *[optional]* list of properties of the filter
     *
     * Returns: 
     *  (TODO) The current query filter hash.
     * 
     * See also:
     *  <plist_to_property_hash>
     */
    this.add_query_filter = function(filter, value, plist){
	
	// Make sure we've defined the group.
	if( typeof(this.query_filters[filter]) === 'undefined' ){
	    this.query_filters[filter] = {};
	}

	this.query_filters[filter][value] = this.plist_to_property_hash(plist);
	
	//ll("Current state: " + bbop.dump(this.query_filters));

	return {}; // TODO
    };

    /*
     * Function: remove_query_filter
     *
     * Remover for query filters ('fq'), is a plist is specified, it
     * will only remove if all of the listed criteria are met.
     *
     * Parameters: 
     *  filter - filter (type) string
     *  value - filter value string (TODO: or defined logic hash)
     *  plist - *[optional]* list of properties of the filter
     *
     * Returns: 
     *  boolean (on success)
     */
    this.remove_query_filter = function(filter, value, plist){

	// Default return value.
	var retval = false;

	// Internal helper to delete a low level key, and then if the
	// top-level is empty, get that one too.
	function _full_delete(hash, key1, key2){
	    if( key1 && key2 && hash &&
		hash[key1] && hash[key1][key2] ){
		    delete hash[key1][key2];
		}
	    if( us.isEmpty(hash[key1]) ){
		delete hash[key1];
	    }
	}

	// If we have a filter, a value, and it's there...
	if( filter && value &&
	    anchor.query_filters[filter] &&
	    anchor.query_filters[filter][value] ){

		// If no real plist hash been defined, just go ahead
		// and get rid of that. Otherwise, make sure that the
		// defined plist and the stored properties are the
		// same before deleting.
		if( ! plist || us.isEmpty(plist) ){
		    _full_delete(anchor.query_filters, filter, value);
		    retval = true;
		}else{
		    
		    var filter_phash = anchor.query_filters[filter][value];
		    var in_phash = anchor.plist_to_property_hash(plist);
		    
		    if( bbop.is_same(filter_phash, in_phash) ){		
			_full_delete(anchor.query_filters, filter, value);
			retval = true;
		    }
		}
	    }

	return retval;
    };

    /*
     * Function: reset_query_filters
     *
     * Reset the query filters ('fq'); but leave sticky filters alone.
     *
     * Parameters: 
     *  n/a
     * 
     * Returns: 
     *  (TODO) The current query filter hash.
     */
    this.reset_query_filters = function(){

	// Drill down and delete all non-stickies.
	each(anchor.query_filters, function(values, filter){
	    //ll('filter: ' + filter);
	    each(values, function(props, value){
		//ll('  value: ' + value);
		var sticky_p = props['sticky_p'];
		if( ! sticky_p ){
		    //ll('hit: ' + filter + ', ' + value);
		    anchor.remove_query_filter(filter, value);
		}
	    });
	});
	
	return {}; // TODO
    };

    /*
     * Function: get_query_filter_properties
     *
     * Get a hash representing a query filter ('fq').
     *
     * Parameters: 
     *  key - filter string (TODO: or defined logic hash)
     *
     * Returns: 
     *  The current query filter hash for key.
     */
    this.get_query_filter_properties = function(filter, value){

	// Default return value.
	var retobj = null;
	
	// If we have a key and it's there...
	var aqf = anchor.query_filters;
	if( filter && value && aqf[filter] && aqf[filter][value] ){
	    retobj =
		{
		    'filter' : filter,
		    'value' : value,
		    //'polarity': aqf[filter][value]['negative_p'],
		    'negative_p': aqf[filter][value]['negative_p'],
		    'sticky_p': aqf[filter][value]['sticky_p']
		};
	}

	return retobj;
    };

    /*
     * Function: get_query_filters
     *
     * Get a list of hashes representing the query filters ('fq'). The
     * return lists look like:
     *
     * : [{'filter': A, 'value': B, 'negative_p': C, 'sticky_p': D}, ...]
     *
     * Where A and B are strings and C and D are booleans.
     * 
     * Parameters: 
     *  n/a
     *
     * Returns: 
     *  A list of the current query filter hashs.
     */
    this.get_query_filters = function(){

	var retlist = [];	
	each(anchor.query_filters, function(values, f){
	    each(values, function(props, v){
		retlist.push(anchor.get_query_filter_properties(f, v));
	    });
	});

	return retlist;
    };

    /*
     * Function: get_sticky_query_filters
     *
     * Get a list of hashes representing the current stucky query
     * filters ('fq'). See <get_query_filters> for a specification of
     * what the return type looks like.
     * 
     * Parameters: 
     *  n/a
     *
     * Returns: 
     *  A list of the current sticky query filter hashs.
     * 
     * See also:
     *  <get_query_filters>
     */
    this.get_sticky_query_filters = function(){

	var retlist = [];	
	each(anchor.query_filters, function(values, f){
	    each(values, function(props, v){
		var qfp = anchor.get_query_filter_properties(f, v);
		if( qfp['sticky_p'] === true ){
		    retlist.push(qfp);			      
		}
	    });
	});

	return retlist;
    };

    // A little extra thing that we might need sometimes.
    this.query_extra = null;

    // Spaces can cause problems in URLs in some environments.
    //final_qurl = final_qurl.replace(/ /g, '%20');
    // Convert the URL into something more usable.
    // Because we internally use %09 as a special case, make sure
    // we don't double-up on it.
    this._query_encode = function(str_to_encode){

	var fs1 = encodeURI(str_to_encode);
	var fs2 = fs1.replace(/\%2509/g, '%09');

	var final_encoding = fs2;
	return final_encoding;
    };

    /*
     * Function: filter_list_to_assemble_hash
     *
     * Get all of our query filter variables and try and make
     * something of them that <get_assemble> can understand.
     *
     * Sticky doesn't matter here, but negativity does. However, we
     * can be pretty naive since the hashing should have already taken
     * out mutually exclusive dupes.
     * 
     * The argument is a list of query filter properties, as returned
     * by <get_query_filters> and <get_sticky_query_filters>.
     *
     * Parameters:
     *  flist - a list of query filter properties (see above)
     *
     * Returns:
     *  hash of filter names to value lists
     * 
     * See also:
     *  <get_query_filters>
     *  <get_sticky_query_filters>
     */
    this.filter_list_to_assemble_hash = function(flist){
	var h = {};
	each(flist, function(filter_property){
	    
	    // Grab only the properties that affect the
	    // URL.
	    var filter = filter_property['filter'];
	    var value = filter_property['value'];
	    var negative_p = filter_property['negative_p'];
	    
	    // We need to alter at the filter level.
	    if( negative_p ){
		filter = '-' + filter;
	    }
	    
	    // Make sure it is defined.
	    if( typeof(h[filter]) === 'undefined' ){
		h[filter] = [];
	    }
	    h[filter].push(value);
	});
	return h;
    };
    
    /*
     * Function: sensible_query_p
     * 
     * Simply ask the manager if a free text query ('q') makes sense
     * at this point.
     * 
     * This currently means that the query text ('q') is one of:
     *  1) the same as the default query
     *  2) the same as the fundamental query
     *  3) three (3) (or whatever minimal_query is set to) characters long
     *  4) the empty string ('')
     * and that query fields ('qf') are defined.
     * 
     * This is an overridable opinion of the manager.
     * 
     * Parameters:
     *  n/a
     *
     * Returns:
     *  boolean
     */
    this.sensible_query_p = function(){
	var retval = false;
	var q = anchor.get_query();
	var qf = anchor.query_field_set();

	//console.log('q', q);
	//console.log('qf', qf);

	if( qf && ! us.isEmpty(qf) ){ // first hurdle

	    if( q === anchor.get_default_query() ){
		retval = true;
	    }else if( q === anchor.get_fundamental_query() ){
		retval = true;
	    }else if( q && q.length >= anchor.minimal_query_length ){
		retval = true;
	    }else if( q === ''){
		retval = true;
	    }
	}
	return retval;
    };

    /*
     * Function: last_packet_sent
     *
     * It is up to the UI to do something interesting with this number.
     * 
     * Also remember that this number only rises through calls to
     * <update> or one of its wrappers. Calls to <get_query_url> and
     * the like will not affect this number.
     * 
     * Parameters:
     *  n/a 
     *
     * Returns:
     *  integer
     * 
     * See also:
     *  <update>
     */
    this.last_packet_sent = function(){
    	return anchor.last_sent_packet;
    };

    /*
     * Function: clear
     *
     * Clear all non-sticky query parameters to get back to a more
     * "original" state.
     * 
     * Not to be confused with <reset>.
     * 
     * Parameters: 
     *  n/a
     *
     * Returns:
     *  n/a
     */
    this.clear = function(){

	// Reset 'q'.
	anchor.query = anchor.default_query;

	// Reset 'fq', all but sticky.
	anchor.reset_query_filters();
    };

    /*
     * Function: reset
     *
     * Manually trigger the "reset" chain of events.
     *
     * This is a curried wrapper for <update> and should be preferred
     * over a direct call to update.
     *
     * Note to be confused with <clear>.
     *
     * Returns:
     *  the query url (with the jQuery callback specific parameters)
     * 
     * See also:
     *  <update>
     */
    this.reset = function(){
	return anchor.update('reset');
    };

    /*
     * Function: search
     *
     * Trigger the "search" chain of events.
     * Takes a field-keyed hash of bbop.logics as an argument.
     * 
     * This is a curried wrapper for <update> and should be preferred
     * over a direct call to update.
     * 
     * Parameters:
     *  n/a
     *
     * Returns:
     *  the query url (with the jQuery callback specific parameters)
     * 
     * See also:
     *  <update>
     */
    this.search = function(){
	return anchor.update('search');
    };

    /*
     * Function: page
     *
     * Re-trigger the "search" chain of events, but with the variables
     * set for a different section of the results.
     * 
     * Note that this operates independently of any impossibilites in
     * the results--just how such paging would look and
     * triggering. Ths UI should handle impossibilities and the like.
     * 
     * This is a wrapper for <update> and should be preferred over a
     * direct call to update.
     * 
     * Parameters: 
     *  rows - the number of rows to return
     *  start - the offset of the rows to return
     *
     * Returns:
     *  the query url (with the jQuery callback specific parameters)
     * 
     * See also:
     *  <update>
     */
    this.page = function(rows, start){
	anchor.set('rows', rows);
	anchor.set('start', start);
	return anchor.update('search', rows, start);
    };

    /*
     * Function: page_first
     *
     * Currently a convenience alias for <search>. Think about it--it
     * makes sense.
     * 
     * This is a wrapper for <page> and should be preferred over a
     * direct call to page.
     * 
     * Parameters: 
     *  n/a
     *
     * Returns:
     *  n/a
     * 
     * See also:
     *  <page>
     */
    this.page_first = anchor.search;
    
    /*
     * Function: page_previous
     * 
     * This is a wrapper for <page> and should be preferred over a
     * direct call to page.
     * 
     * Parameters: 
     *  n/a
     *
     * Returns:
     *  the query url (with the jQuery callback specific parameters)
     * 
     * See also:
     *  <page>
     */
    this.page_previous = function(){
	var do_rows = anchor.get_page_rows();
	var do_offset = anchor.get_page_start() - do_rows;
	return anchor.page(do_rows, do_offset);
    };
    
    /*
     * Function: page_next
     * 
     * This is a wrapper for <page> and should be preferred over a
     * direct call to page.
     * 
     * Parameters: 
     *  the query url (with the jQuery callback specific parameters)
     *
     * Returns:
     *  n/a
     * 
     * See also:
     *  <page>
     */
    this.page_next = function(){
	var do_rows = anchor.get_page_rows();
	var do_offset = anchor.get_page_start() + do_rows;
	return anchor.page(do_rows, do_offset);
    };
    
    /*
     * Function: page_last
     * 
     * Trigger search on last page parameters.
     * 
     * Since the manager has no idea about what is actually being
     * returned, the real world number of total documents needs to be
     * added as an argument.
     * 
     * This is a wrapper for <page> and should be preferred over a
     * direct call to page.
     * 
     * Parameters: 
     *  total_document_count - integer for the total number of docs found
     *
     * Returns:
     *  the query url (with the jQuery callback specific parameters)
     * 
     * See also:
     *  <page>
     */
    this.page_last = function(total_document_count){
	var do_rows = anchor.get_page_rows();
	var mod = total_document_count % do_rows;
	var do_offset = total_document_count - mod;
	// ll("page_last: " + total_document_count + " " +
	//    do_rows + " " + mod + " " + do_offset);
	var ret = null;
	if( mod === 0 ){
	    ret = anchor.page(do_rows, do_offset - do_rows);
	}else{
	    ret = anchor.page(do_rows, do_offset);
	}
	return ret;
    };

    /*
     * Function: get_page_rows
     *
     * Return the number of rows the manager is currently set
     * to. Useful as an argument to <page>.
     * 
     * Parameters: 
     *  n/a
     *
     * Returns:
     *  integer; the number of rows the manager is currently set to
     * 
     * See also:
     *  <page>
     */
    this.get_page_rows = function(){
	return anchor.get('rows');
    };

    /*
     * Function: get_page_start
     *
     * Return the rows offset the manager is currently set to. Useful
     * as an argument to <page>.
     * 
     * Parameters: 
     *  n/a
     *
     * Returns:
     *  integer; the offset the manager is currently set to
     * 
     * See also:
     *  <page>
     */
    this.get_page_start = function(){
	return anchor.get('start');
    };

    /*
     * Function: add_query_field
     * 
     * Add a new query field to the query. 
     * 
     * This does not go through and expand into searchable fields, for
     * that see: <query_field_set>.
     *
     * Parameters: 
     *  qf - the query field to add
     *  boost - *[optional]* defaults to 1.0
     *
     * Returns:
     *  true or false on whether or not it is a new field
     * 
     * See also:
     *  <query_field_set>
     */
    this.add_query_field = function(qf, boost){
	
	var retval = false;

	// Make sure that some boost is there.
	if( typeof(boost) === 'undefined' ){
	    boost = 1.0;
	}

	// Check.
	if( typeof(anchor.query_fields[qf]) === 'undefined' ){
	    retval = true;
	}

	// Add.
	anchor.query_fields[qf] = boost;

	return retval;
    };

    /*
     * Function: query_field_set
     *
     * Bulk getter/setter for the query fields--the fields that are
     * searched (and by what weight) when using a query ('q' or
     * set_query(), i.e. the 'qf' field).
     * 
     * This will always use searchable fields if possible,
     * automatically replacing the non-searchable versions (I can't
     * think of any reason to use non-searchable versions unless you
     * want your searches to not work) if a personality is set. If no
     * personality is set, it will just use the arguments as-is.
     * 
     * The argument replaces the current set.
     *
     * The qfs argument should be a hash like:
     * 
     *  {'field01': value01, ...}
     * 
     * Parameters: 
     *  qfs - *[optional]* query fields to set
     *
     * Returns:
     *  the current query_fields array (e.g. ["field01^value01", ...])
     */
    this.query_field_set = function(qfs){

	// Covenience.
	var cclass = anchor._current_class;

	// Only do something if we have a query field set.
	if( qfs ){
	    
	    // Only do the probing if a personality has been set.
	    if( cclass ){

		// Get the current searchable extension string from
		// the personality class.
		//var s_ext = cclass.searchable_extension();
		// Actually, we're going to make this non-variable.
		var s_ext = '_searchable';

		// Probe the input to see if there are any searchable
		// alternatives to try, use those instead.
		var searchable_qfs = {};
		each(qfs, function(value, filter){
		    // If the probe fails, just put in
		    // whatever is there.
		    var cfield = cclass.get_field(filter);
		    if( cfield && cfield.searchable() ){
			//ll('filter/value:');
			var new_f = filter + s_ext;
			searchable_qfs[new_f] = value;
		    }else{
			searchable_qfs[filter] = value;
		    }
	    	});
		qfs = searchable_qfs;
	    }	    
	    
	    // Overwrite the current.
	    anchor.query_fields = qfs;
	}
	
	// Using the original information, convert them to the
	// proper output format.
	var output_format = [];
	each(anchor.query_fields, function(value, filter){
	    output_format.push(filter + '^' + value);
	});
	return output_format;
    };

    /*
     * Function: facets
     *
     * Bulk getter/setter for facets (technically 'facet.field').
     *
     * Parameters: 
     *  key - *[optional]* facet to add to the facet list
     *
     * Parameters: 
     *  list - *[optional]* list to replace the current list with
     *
     * Returns:
     *  the current facets hash.
     */
    this.facets = function(list_or_key){
	if( list_or_key ){
	    if( bbop.what_is(list_or_key) !== 'array' ){
		// Arrayify it.
		list_or_key = [list_or_key];
	    }else{
		// When there is a list, we are replacing the whole
		// thing, so let's just poof it out of existance.
		anchor.facet_fields = {};
	    }
	    each(list_or_key, function(item){
		anchor.facet_fields[item] = true;
	    });
	}
	return us.keys(anchor.facet_fields);
    };

    /*
     * Function: set_default_query
     *
     * Setter for the default query for the query variable ('q').
     * 
     * Call <reset_query> if you want to affect query immediately.
     * 
     * Parameters: 
     *  new_default_query - new default query string (or TODO: <bbop.logic>)
     *
     * Returns:
     *  the current setting of default query for ('q')
     */
    this.set_default_query = function(new_default_query){
	anchor.default_query = new_default_query;
	return anchor.default_query;
    };

    // /*
    //  * Function: set_first_run_query
    //  *
    //  * Setter for a first run query.  Normally, when <reset_query>, or
    //  * related method, is executed, we reset back to the default
    //  * query. This method sets a one time variable so a non empty
    //  * value can be used for the first reset.
    //  * 
    //  * Call <reset_query> if you want to affect query immediately.
    //  * 
    //  * Parameters: 
    //  *  first_run_query - query_string (or TODO: <bbop.logic>)
    //  *
    //  * Returns:
    //  *  the current setting of default query for ('q')
    //  */
    // this.set_first_run_query = function(first_run_query){
    // 	anchor.default_query = new_default_query;
    // 	return anchor.default_query;
    // };

    /*
     * Function: reset_default_query
     *
     * Reset the default query back to "*:*".
     * 
     * Call <reset_query> if you want to affect query immediately.
     * 
     * Parameters:
     *  n/a
     *
     * Returns:
     *  the current setting of default query ('q')
     */
    this.reset_default_query = function(){
	anchor.default_query = anchor.fundamental_query;
	return anchor.default_query;
    };

    /*
     * Function: set_query
     *
     * Setter for the query variable ('q').
     * 
     * Parameters: 
     *  new_query - new value for the query string (or TODO: <bbop.logic>)
     *
     * Returns:
     *  the current setting of query ('q')
     * 
     * Also see:
     *  <set_comfy_query>
     */
    this.set_query = function(new_query){
	anchor.query = new_query;
	return anchor.query;
    };

    /*
     * Function: set_comfy_query
     *
     * A specialized setter for the query variable ('q'), as follows:
     *
     * If the input is all alphanum or space, the input is
     * tokenized. If there is one token, if it is at least three
     * characters, gets a wildcard '*'; if there are more tokens, the
     * last one gets a wild card.
     * 
     * This might be a more comfortable way to search for most naive
     * (non-power user) interfaces.
     * 
     * Parameters: 
     *  new_query - new value for the query string (or TODO: <bbop.logic>)
     *
     * Returns:
     *  the current setting of query ('q')
     * 
     * Also see:
     *  <set_query>
     */
    this.set_comfy_query = function(new_query){

	var comfy_query = new_query;

	// Check that there is something there.
	if( new_query && new_query.length && new_query.length > 0 ){

	    // Check if the last real input has a space after it.
	    var has_cursor_p = true;
	    if( new_query.slice(-1) === ' ' ){
		has_cursor_p = false;
	    }

	    // Now chomp it down again to get rid of whitespace.
	    new_query = bbop.chomp(new_query);

	    // Check (again) that there is something there.
	    if( new_query && new_query.length && new_query.length > 0 ){

		// That it is alphanum+space-ish and that we actually
		// might want to add a wildcard (i.e. has cursor).
		if( alphanum.test(new_query) && has_cursor_p ){
	    
		    // Break it into tokens and get the last.
		    var tokens = new_query.split(new RegExp('\\s+'));
		    var last_token = tokens[tokens.length -1];
		    //ll('last: ' + last_token);
		
		    if( tokens.length === 1 ){
			
			// If it is three or more, add the wildcard.
			if( last_token.length >= anchor.minimal_query_length ){
			    tokens[tokens.length -1] = last_token + '*';
			}
		    }else{
			tokens[tokens.length -1] = last_token + '*';
		    }
		    // And join it all back into our comfy query.
		    comfy_query = tokens.join(' ');
		}
	    }
	}

	// Kick it back to the normal set_query.
	return anchor.set_query(comfy_query);
    };

    /*
     * Function: set_id
     *
     * A limited setter, removing whatever else is on query. This is
     * for when you want to lock into one (unique) document by id
     * (essentially 'q=id:"foo"'). All other query operations behave
     * as they should around it.
     * 
     * Parameters: 
     *  new_id - string id
     *
     * Returns:
     *  the current setting of query ('q')
     * 
     * Also see:
     *  <set_ids>
     */
    this.set_id = function(new_id){
	anchor.query = 'id:' + bbop.ensure(new_id, '"');
	return anchor.query;
    };

    // Function to unwind and lock a list if identifiers onto a field.
    function _lock_map(field, id_list){
	var fixed_list = [];
	each(id_list, function(item){
	    fixed_list.push(bbop.ensure(item, '"'));
	});
	
	var base_id_list = '(' + fixed_list.join(' OR ') + ')';

	var ret_query = field + ':' + base_id_list;
	return ret_query;
	
    }

    /*
     * Function: set_ids
     *
     * Like <set_id>, a limited setter. It removes whatever else is on
     * query and replaces it with something like:
     * 
     * : gm.set_ids(['GO:1', 'GO:2'])
     * 
     * This is for when you want to lock into a set of documents by id. All
     * other query operations behave as they should around it.
     * 
     * Parameters: 
     *  id_list - a list of ids to search for
     *
     * Returns:
     *  the current setting of query ('q')
     * 
     * Also see:
     *  <set_ids>
     */
    this.set_ids = function(id_list){
	anchor.query = _lock_map('id', id_list);
	return anchor.query;
    };

    /*
     * Function: set_targets
     *
     * Like a more generalized version of <set_ids>, a limited. It
     * removes whatever else is on query and replaces it with
     * something like:
     * 
     * : gm.set_targets(['GO:1', 'GO:2'], ['field_1', 'field_2'])
     * 
     * This is for when you want to lock into a set of documents by
     * locking onto identifiers in some set of search fields. All
     * other query operations behave as they should around it.
     * 
     * Parameters: 
     *  id_list - a list of ids to search for
     *  field_list - a list of fields ids to search across
     *
     * Returns:
     *  the current setting of query ('q')
     * 
     * Also see:
     *  <set_ids>
     */
    this.set_targets = function(id_list, field_list){

	var fixed_list = [];
	each(field_list, function(field){
	    fixed_list.push(_lock_map(field, id_list));
	});

	var sum = fixed_list.join(' OR ');

	anchor.query = sum;
	return anchor.query;
    };

    /*
     * Function: get_query
     *
     * Getter for the query variable ('q').
     * 
     * Parameters: 
     *  n/a
     *
     * Returns:
     *  the current setting of extra
     */
    this.get_query = function(){
	return anchor.query;
    };

    /*
     * Function: get_default_query
     *
     * Getter for what the query variable 'q' will be set to on a
     * <reset_query>.
     * 
     * Parameters: 
     *  n/a
     *
     * Returns:
     *  the current setting of the default query
     */
    this.get_default_query = function(){
	return anchor.default_query;
    };

    /*
     * Function: get_fundamental_query
     *
     * Getter for what the query variable 'q' will be set to on a
     * <reset_default_query>.
     * 
     * Parameters: 
     *  n/a
     *
     * Returns:
     *  the current setting of the fundamental default query
     */
    this.get_fundamental_query = function(){
	return anchor.fundamental_query;
    };

    /*
     * Function: get_query
     *
     * Getter for the query variable ('q').
     * 
     * Parameters: 
     *  n/a
     *
     * Returns:
     *  the current setting of extra
     */
    this.get_query = function(){
	return anchor.query;
    };

    /*
     * Function: reset_query
     *
     * Remove/reset the query variable ('q'); this set it back to the
     * default query.
     *
     * Parameters:
     *  none
     *
     * Returns:
     *  the current value of query
     * 
     * Also see:
     *  <set_default_query>
     *  <reset_default_query>
     */
    this.reset_query = function(){
	anchor.query = anchor.default_query;
	ll('reset query to default: ' + anchor.query);
	return anchor.query;
    };

    /*
     * Function: set_extra
     *
     * Setter for the internal string variable to be appended to the
     * end of a query. For special use cases only (e.g. extend
     * functionality of the API safely).
     * 
     * Parameters: 
     *  new_extra - *[optional]* new value for the extras string
     *
     * Returns:
     *  the current setting of extra
     */
    this.set_extra = function(new_extra){
	anchor.query_extra = new_extra;
	return anchor.query_extra;
    };

    /*
     * Function: get_extra
     *
     * Getter for the internal string variable to be appended
     * to the end of a query.
     *
     * Parameters: 
     *  n/a
     *
     * Returns:
     *  the current setting of extra
     */
    this.get_extra = anchor.set_extra;

    /*
     * Function: remove_extra
     *
     * Remove/reset the extra bit.
     *
     * Parameters:
     *  none
     *
     * Returns:
     *  ""
     */
    this.remove_extra = function(){
	anchor.query_extra = "";
	return anchor.query_extra;
    };

    /*
     * Function: set
     *
     * Set an internal variable for the query. The internal variables
     * are typically things like 'qt', 'indent', etc.--things that you
     * might set and forget a while. It does /not/ include highly
     * dynamic variables (like callback and packet) or querying
     * variables like 'q' and 'fq'; for those you need to use the API.
     *
     * Parameters: 
     *  key - the name of the parameter to change
     *  new_val - what you want the new value to be
     *
     * Returns:
     *  n/a
     */
    this.set = function(key, new_val){
	anchor.query_variants[key] = new_val;
    };

    /*
     * Function: get
     *
     * Get an internal variable for the query.
     *
     * See <set> for the kinds of parameters that can be read.
     * 
     * Parameters: 
     *  key - the name of the parameter to get
     *
     * Returns:
     *  The found value of the key.
     */
    this.get = function(key){
	return anchor.query_variants[key];
    };

    /*
     * Function: unset
     *
     * Unset (remove) an internal variable for the query. Only usable on certain types of 
     * 
     * Only use is you really know what you're doing.
     *
     * Parameters: 
     *  key - the name of the parameter to unset/remove
     *
     * Returns:
     *  boolean; true false on whether the key was found
     */
    this.unset = function(key){
	var retval = false;

	if( typeof(anchor.query_variants[key]) !== 'undefined' ){
	    retval = true;
	    delete anchor.query_variants[key];
	}

	return retval;
    };

    /*
     * Function: include_highlighting
     *
     * Turn hilighting on or off (with true or false).
     * 
     * This essentially adds the parameters to the query string to
     * make sure that basic highlighting on the search is returned.
     * 
     * It starts off as false. The optional html_elt_str argument
     * defaults to:
     *  : <em class="hilite">
     *
     * Parameters: 
     *  hilite_p - *[optional]* boolean
     *  html_elt_str - *[serially optional]* the HTML element string to use
     *
     * Returns:
     *  either false or the current string being used for the return element
     */
    this.include_highlighting = function(hilite_p, html_elt_str){
	var retval = false;

	if( typeof(hilite_p) !== 'undefined' &&
	    (hilite_p === true || hilite_p === false) ){
	    if( hilite_p === true ){

		// Set the default string if necessary.
		if( ! html_elt_str ){ html_elt_str = '<em class="hilite">'; }

		// Set the parameters.
		anchor.set('hl', 'true');
		anchor.set('hl.simple.pre', html_elt_str);

        anchor.set('hl.snippets', anchor.default_hl_snippets);

		// And the retval is not longer false.
		retval = html_elt_str;

	    }else{
		
		// Unset the parameters.
		anchor.unset('hl');
		anchor.unset('hl.simple.pre');
		anchor.unset('hl.snippets');
	    }

	}else{
	    // Otherwise, just discover the current state and return
	    // it.
	    var cl_tmp = anchor.get('hl.simple.pre');
	    if( typeof(cl_tmp) !== 'undefined' ){
		retval = cl_tmp;
	    }
	}

	return retval;
    };

    /*
     * Function: set_personality
     *
     * While we are always contacting the same Solr instance, we
     * sometimes want to have different weights, facets, etc. This
     * function allows us to use the pre-set ones defined in the
     * constructor configuration argument.
     * 
     * Currently, this only sets the 'facet.field' internal variable.
     *
     * Parameters: 
     *  personality_id - string
     *
     * Returns:
     *  Will return false if personality doesn't exist
     */
    this.set_personality = function(personality_id){
	var retval = false;

	// This sets the facet.field internal variable.
	var cclass = anchor._golr_conf.get_class(personality_id);
	if( cclass ){

	    // Remember what our personality is.
	    // WARNING: this line must go before the query_field_set
	    // line below, or else we won't get the "smart" search.
	    this._current_class = cclass;

	    // Set the facets for our class.
	    anchor.facets(cclass.field_order_by_weight('filter'));

	    // Set the query field weights ('qf') necessary to make
	    // queries run properly.
	    anchor.query_field_set(cclass.get_weights('boost'));
	    
	    // Show that we did indeed set a personality.
	    retval = true;
	}

	return retval;
    };

    /*
     * Function: get_personality
     *
     * Returns the current personality, null if none.
     * 
     * Parameters: 
     *  n/a
     *
     * Returns:
     *  Returns the current personality as a string, null if none is set
     */
    this.get_personality = function(){
	var retval = null;

	if( typeof(anchor._current_class) !== 'undefined' &&
	    bbop.what_is(anchor._current_class) === 'golr-conf.conf_class'){
	    retval = anchor._current_class.id();
	}

	return retval;
    };

    /*
     * Function: get_query_url
     *
     * Get the current invariant state of the manager returned as a
     * encoded URL string (using encodeURI()).
     * 
     * This means the URL for the current query to the GOlr store, but
     * without extra information about packets, callbacks, and the
     * like.
     * 
     * This is generally appropriate for getting data, but maybe not
     * for things like high-speed autocomplete where races can
     * occur. For those, you might want to consider <update> or
     * <search>.
     *
     * Parameters:
     *  n/a
     * 
     * Returns:
     *  URL string
     * 
     * Also see:
     *  <update>, <search>
     */
    this.get_query_url = function(){

	// Structure of the necessary invariant parts.	
	var qurl = anchor._solr_url + 'select?';

	// Filters to assemble.
	var assemf = anchor.get_query_filters();
	var fq = anchor.filter_list_to_assemble_hash(assemf);

	// Add all of our different specialized hashes.
	var things_to_add = [
	    //bbop.get_assemble(anchor.query_invariants),
	    //bbop.get_assemble(anchor.query_facets),
	    bbop.get_assemble(anchor.query_variants),
	    bbop.get_assemble(anchor.current_facet_field_limits),
	    //bbop.get_assemble({'fq': anchor.query_sticky_filters}),
	    bbop.get_assemble({'fq': fq}),
	    bbop.get_assemble({'facet.field':
			       us.keys(anchor.facet_fields)}),
	    bbop.get_assemble({'q': anchor.query}),
	    anchor.query_extra
	];
	// Add query_fields ('qf') iff query ('q') is set and it is
	// not length 0.
	if( anchor.query &&
	    anchor.query.length &&
	    anchor.query.length !== 0 &&
	    anchor.query !== anchor.fundamental_query ){
		var in_qf =
		    bbop.get_assemble({'qf': anchor.query_field_set()});
		things_to_add.push(in_qf);
	    }
	
	// Assemble the assemblies into a single URL, throw out
	// everything that seems like it isn't real to keep the URL as
	// clean a possible.
	var filtered_things =
		bbop.pare(things_to_add, function(item, index){
		    var retval = true;
		    if( item && item !== '' ){
			retval = false;
		    }
		    return retval;
		});

	var final_qurl = qurl + filtered_things.join('&');
	final_qurl = anchor._query_encode(final_qurl);
	ll('qurl: ' + final_qurl);
    	return final_qurl;
    };

    /*
     * Function: push_excursion
     *
     * Save the current state of the manager--data and sticky filter
     * information--onto an internal stack. Batch information is not
     * stored.
     * 
     * Useful for gettinginto a state, doing something else, then
     * returning to the original state.
     * 
     * Parameters:
     *  n/a
     * 
     * Returns:
     *  the number of items on the excursion stack
     * 
     * Also see:
     *  <get_query_url>
     *  <pop_excursion>
     */
    this.push_excursion = function(){
	
	var now = {
	    // Save current state (data).
	    data_url: anchor.get_query_url(),
	    // Save current state (session).
	    session: {
		// Get the sticky filters.
		sticky_filters: anchor.get_sticky_query_filters()
	    }
	};

	// Save.
	anchor._excursions.push(now);

	// ...
    	return anchor._excursions.length;
    };

    /*
     * Function: pop_excursion
     *
     * Return to a previously pushed state. Batch items are not
     * recovered.
     * 
     * Parameters:
     *  n/a
     * 
     * Returns:
     *  boolean on whether a state was recovered
     * 
     * Also see:
     *  <get_query_url>
     *  <gpush_excursion>
     */
    this.pop_excursion = function(){
	
	var retval = false;

	var then = anchor._excursions.pop();
	if( then ){
	    retval = true;

	    // Recover data state.
	    var then_data_url = then['data_url'];
	    anchor.load_url(then_data_url);

	    // Recover the session state.
	    var then_session_stickies = then['session']['sticky_filters'];
	    // Add the sticky filters.
	    each(then_session_stickies, function(sticky){
		var flt = sticky['filter'];
		var fvl = sticky['value'];
		var fpl = [];
		if( sticky['negative_p'] === true ){
		    fpl.push('-');
		}
		if( sticky['sticky_p'] === true ){
		    fpl.push('*');
		}
		anchor.add_query_filter(flt, fvl, fpl);
	    });	    
	}
	
    	return retval;
    };

    /*
     * Function: get_download_url
     *
     * Get the current invariant state of the manager returned as a
     * URL string.
     * 
     * This differs from <get_query_url> in that the generated string
     * is intended for text-processing uses rather than computerized
     * searching uses. The idea where is to create a TSV file for
     * downloading and consumption.
     * 
     * Instead of downloading all of the results, a limited listed set
     * can be downloaded using entity_list, which identifies documents by id.
     * 
     * The optional argument hash looks like:
     *  rows - the number of rows to return; defaults to: 1000
     *  encapsulator - how to enclose whitespace fields; defaults to: ""
     *  separator - separator between fields; defaults to: "%09" (tab)
     *  header - whether or not to show headers; defaults to: "false"
     *  mv_separator - separator for multi-valued fields; defaults to: "|"
     *  entity_list - list of specific download items in results; default null
     * 
     * With the entity list, keep in mind that null and an empty list
     * are handled in pretty much the same way--they are an indication
     * that we are going after nothing specific, and so all results
     * are game.
     * 
     * Parameters:
     *  field_list - a list of fields to return
     *  in_arg_hash - *[optional]* additional optional arguments
     * 
     * Returns:
     *  URL string
     * 
     * Also see:
     *  <get_query_url>
     */
    this.get_download_url = function(field_list, in_arg_hash){
	
	// Save current state.
	anchor.push_excursion();

	// Deal with getting arguments in properly.
	var default_hash =
	    {
		rows : 1000,
		encapsulator : '',
		separator : "%09",
		header : 'false',
		mv_separator : "|",
		entity_list : [],
		golr_download_url : null
	    };
	var arg_hash = bbop.fold(default_hash, in_arg_hash);

	// Make the changes we want.
	anchor.set('wt', 'csv');
	anchor.set('start', 0);
	anchor.set('fl', field_list.join(','));
	anchor.set('rows', arg_hash['rows']);
	anchor.set('csv.encapsulator', arg_hash['encapsulator']);
	anchor.set('csv.separator', arg_hash['separator']);
	anchor.set('csv.header', arg_hash['header']);
	anchor.set('csv.mv.separator', arg_hash['mv_separator']);

	// A little more tricky, jimmy the entity list into the query
	// if it's viable.
	var entity_list = arg_hash['entity_list'];
	if( typeof(entity_list) !== 'undefined' &&
	    us.isArray(entity_list) &&
	    entity_list.length > 0 ){
		anchor.set_ids(entity_list);
	}

	// Get url.
	var returl = anchor.get_query_url();

	// If defined, switch download URL to something else.
	if( arg_hash.golr_download_url ){
	    returl = returl.replace(anchor._solr_url,
				    arg_hash.golr_download_url);
	}

	// Reset the old state.
	anchor.pop_excursion();

    	return returl;
    };

    /*
     * Function: get_filter_query_string
     *
     * Get the current state of the manager, as defined by the current
     * gross filter set--query, sticky filters, and standard filters--
     * returned as a URL query string (sans the '?').
     * 
     * This differs from <get_query_url> and <get_state_url> in that
     * the generated string is intended for applications that may want
     * just enough information to recover filter state when the
     * personality, and other types of information, are already
     * known. It is intended to be part of a light RESTy bookmarking
     * mechanism in larger application.
     * 
     * Parameters:
     *  n/a
     * 
     * Returns:
     *  query string for current filters (sans the '?')
     * 
     * Also see:
     *  <get_query_url>
     *  <get_state_url>
     */
    this.get_filter_query_string = function(){
	
	// // Save current state.
	// anchor.push_excursion();

	var q = anchor.get_query();

	// Get the filters and sort them into sticky and "normal"
	// sets.
	var filters = anchor.get_query_filters();
	var std_filters = [];
	var sticky_filters = [];
	each(filters, function(filter){
	    if( filter['sticky_p'] ){
		sticky_filters.push(filter);
	    }else{
		std_filters.push(filter);
	    }
	});
	
	var fq = anchor.filter_list_to_assemble_hash(std_filters);
	var sfq = anchor.filter_list_to_assemble_hash(sticky_filters);

	var things_to_add = [];
	if( q ){
	    things_to_add.push(bbop.get_assemble({'q': q}));
	}
	if( ! us.isEmpty(fq) ){
	    things_to_add.push(bbop.get_assemble({'fq': fq}));
	}
	if( ! us.isEmpty(sfq) ){
	    things_to_add.push(bbop.get_assemble({'sfq': sfq}));
	}
	    
	// // Reset the old state.
	// anchor.pop_excursion();

	var final_qstr = things_to_add.join('&');
	final_qstr = anchor._query_encode(final_qstr);
    	return final_qstr;
    };

    /*
     * Function: get_state_url
     *
     * Get the current invariant state of the manager, plus the
     * current personality as a parameter, returned as a URL string.
     * 
     * This differs from <get_query_url> in that the generated string
     * is intended for applications that may want a little more
     * information and hinting over just what the current search
     * is. This method essentially parameterizes some of the "hidden
     * state" of the manager.
     * 
     * Parameters:
     *  n/a
     * 
     * Returns:
     *  URL string
     * 
     * Also see:
     *  <get_query_url>
     */
    this.get_state_url = function(){
	
	// Save current state.
	anchor.push_excursion();

	// Make the changes we want. First, physically set the
	// "personality", then set pins for jump-in recovery.
	anchor.set('personality', anchor.get_personality());

	// Explicitly set sticky pins for later recovery.
	// Do this pretty much exactly like we do for get_query_url().
	var sticky_filters = anchor.get_sticky_query_filters();
	var sfq = anchor.filter_list_to_assemble_hash(sticky_filters);
	anchor.set('sfq', sfq);
	
	// Get url.
	var returl = anchor.get_query_url();

	// Reset the old state.
	anchor.pop_excursion();

    	return returl;
    };

    /*
     * Function: load_url
     *
     * Makes a a best attempt to recover the state of a manager from
     * the clues left in a data url. This can also (and probably
     * should) be thought of as a "load bookmark"
     * function. Theoretically, you should even be able to use
     * "bookmarks" from alien installations.
     * 
     * Note that while this recovers enough to get the same data,
     * certain "session"/"preference" type things that are not encoded
     * in the url (e.g. filter stickiness, the contents of batch
     * queues, non-default base queries, etc.) will not be replayed
     * and must be recovered or guessed on an app by app basis..
     * 
     * Warning: this currently only replays a small subset of possible
     * parameters. Currently: personality, q, fq, ???. In the future,
     * this should no all non-session information.
     * 
     * Warning: Because there is more to bookmarks than just the major
     * stuff, variants not supplied in the bookmark will be removed.
     * 
     * This returns true if the parameter portions of the new and
     * bookmark urls match. However, this is often not the case--think
     * shifting personalities, etc.
     * 
     * Parameters:
     *  url - A URL string generated by a manager's get_query_url (or similar)
     * 
     * Returns:
     *  boolean
     */
    this.load_url = function(url){

	// // Some Regexps that would be nice to just compile once.
	// var regexp_url_space = /\%20/g; // '%20' === ' '
	// var regexp_url_quote = /\%22/g; // '%22' === '"'
	// var regexp_url_left_paren = /\%28/g; // '%28' === '('
	// var regexp_url_right_paren = /\%29/g; // '%29' === ')'

	// We are assuming that we are consuming our own URLs from
	// get_query_url(), so we start by attempting to decode it
	// (TODO: need a tab watch here)?
	var decoded_url = decodeURI(url);

	// Break down url.
	var in_params = bbop.url_parameters(decoded_url);

	// First, look for the personality setting and invoke it if
	// it's there--it will dominate unless we take care of it first.
	// Also note the all the keys that we see (for later erasure
	// of excess).
	var seen_params = {};
	each(in_params, function(ip){
	    var key = ip[0];
	    var val = ip[1];
	    if( key === 'personality' && val && val !== '' ){
		anchor.set_personality(val);
	    }
	    seen_params[key] = true;
	});
	
	// Now cycle through the the parameters again and invoke the
	// appropriate functions to bring them in line.
	var sticky_cache = {};
	each(in_params, function(ip){
	    var key = ip[0];
	    var val = ip[1];
	    if( typeof(val) !== 'undefined' && val !== '' ){
		if( key === 'personality' ){
		    // Already did it, skip.
		}else if( key === 'q' ){
		    anchor.set_query(val);
		}else if( key === 'fq' || key === 'sfq' ){
		    // Split the fq (or sfq) parameter.
		    var fnv = bbop.first_split(':', val);
		    var fname = fnv[0];
		    var fval = fnv[1];
		    //ll('HERE: fname: ' + fname);
		    //ll('HERE: fval: ' + fval);
		    if( fname && fval ){
			
			var plist = [];
			
			// Remove leading sign on a filter and
			// add it to the plist.
			var lead_char = fname.charAt(0);
			if( lead_char === '-' || lead_char === '+' ){
			    plist.push(lead_char);
			    fname = fname.substr(1, fname.length -1);
			}
			
			// // TODO: 
			// // If the fval looks like it has not been
			// // decoded (like from a URL-safe
			// // bookmark), go ahead and do so.
			// fval = fval.replace(regexp_url_space, ' ');
			// fval = fval.replace(regexp_url_quote, '"');
			// fval = fval.replace(regexp_url_left_paren, '(');
			// fval = fval.replace(regexp_url_right_paren,')');
			
			// Do not allow quotes in--they will be
			// added by the assembler.
			fval = bbop.dequote(fval);
			
			// Make it sticky it it came in on "sfq".
			// Note if this is the sticky form.
			var skey = fname + '^' + fval;
			if( key === 'sfq' ){
			    sticky_cache[skey] = true;
			    plist.push('*');
			}
			
			// Add the query filter properly, but
			// only if we have not already added the
			// sticky form (prevent clobbering).
			if( ! bbop.is_defined(sticky_cache[skey]) ||
			    key === 'sfq'){
			    anchor.add_query_filter(fname, fval, plist);
			    
			}
		    }
		}else if( key === 'qf' ){
		    // qf is handles a little strangely...
		    var foo = bbop.first_split('^', val);
		    //ll('qf: key: '+ key +', val: '+ val +', foo: '+ foo);
		    anchor.add_query_field(foo[0], foo[1]);
		}else if( key === 'facet.field' ){
		    anchor.facets(val);
		}else if( key === 'start' || key === 'rows' ){
		    // Numbers need to be handled carefully.
		    if( bbop.what_is(val) === 'string' ){
			val = parseFloat(val);
		    }
		    anchor.set(key, val);
		}else{
		    // This one catches all of the non-special
		    // parameters and resets them using .set().
		    anchor.set(key, val);
		    // if( key === 'fq' ){
		    //     throw new Error("OI");			     
		    // }
		}
	    }
	});
	
	// Now go through and remove all of the query variant
	// parameters that were not seen in the bookmark.
	each(anchor.query_variants, function(val, key){
	    if( typeof(seen_params[key]) === 'undefined'){
		anchor.unset(key);
	    }
	});
	
	// Produce our own url from what we've done. If the parameters
	// match with the incoming argument's return true.
	var curr_url = anchor.get_query_url();
	var curr_params = bbop.url_parameters(curr_url);
	var differences = 0;
	if( in_params.length === curr_params.length ){
	    each(in_params, function(in_p, i){
		var curr_p = curr_params[i];
		if( in_p.length === curr_p.length ){
		    if( in_p.length === 1 ){
			if( in_p[0] === curr_p[0] ){
			    // match!
			}else{
			    differences++;
			}
		    }else if( in_p.length === 2 ){
			if( in_p[0] === curr_p[0] && in_p[1] === curr_p[1] ){
			    // match!
			}else{
			    differences++;
			}
		    }
		}else{
		    differences++;
		}
	    });
	}else{
	    differences++;
	}
	
	// Tally the differences and decides if they're the same.
	var retval = false;
	if( differences === 0 ){
	    retval = true;
	}
    	return retval;
    };

    /*
     * Function: add_to_batch
     *
     * "Save" the current manager state to run later in serial batch
     * mode.
     * 
     * The actual job of running these batches is left to the
     * implementation of the sub-managers; probably in "run_batch".
     * 
     * Parameters:
     *  n/a
     * 
     * Returns:
     *  state url
     */
    this.add_to_batch = function(){
	var qurl = anchor.get_query_url();
	anchor._batch_urls.push(qurl);
    	return qurl;
    };

    /*
     * Function: batch_urls
     *
     * Return a pointer to the current batch urls.
     * 
     * Parameters:
     *  n/a
     * 
     * Returns:
     *  array
     */
    this.batch_urls = function(){
    	return anchor._batch_urls;
    };

    /*
     * Function: next_batch_url
     *
     * Return the next data to be processed, removing it from the
     * batch queue in the process.
     * 
     * Parameters:
     *  n/a
     * 
     * Returns:
     *  state url or null
     */
    this.next_batch_url = function(){
    	return anchor._batch_urls.shift() || null;
    };

    /*
     * Function: reset_batch
     *
     * Clear the currently queued data batch.
     * 
     * The actual job of running these batches is left to the
     * implementation of the sub-managers; probably in "run_batch".
     * 
     * Parameters:
     *  n/a
     * 
     * Returns:
     *  the number of items cleared
     */
    this.reset_batch = function(){
	var num = anchor._batch_urls.length;
	anchor._batch_urls = [];
    	return num;
    };
}