/**
 * This is the core script support for fishnet portal.
 * @author Dave Vieglais <vieglais@ku.edu>
 */

/**
 * The FishnetCore class provides a container for maintaining state and a number
 * of convenience functions for interacting with the server the UI.
 * 
 * Requires:
 *   - jQuery
 * 
 * Elements with the following classes have their text property updated:
 * .fn_matching_docs
 * .fn_total_docs
 * .fn_raw_query_string
 * .fn_working_tag
 */
function FishnetCore()
{
  
  this.log = function(msg)
  {
    if (window.console)
      console.debug(msg);
  }
  
  this.pause_updates = false;
  
  /**
   * The total number of documents available in the collection
   */
  this.total_documents = 0;

  /**
   * The total number of documents matching the query
   */
  this.matching_documents = 0;
  
  /**
   * Counter for keeping track of qids
   */
  this.last_qid = 0;
  
  /**
   * List of field descriptions.  Display order.
   * each entry: {name: field name,
   *              label: field label,
   *              unique_count: number of unique terms,
   *              type_code: one of 's', 't', 'i', 'f' (string, text, int, float)
   *              }
   */
  this.fields = [];
  
  /**
   * Cache of histograms loaded to support the UI
   * Index is the field name
   * entry is {name : field name,
   *           label: label for field,
   *           minvalue: the minimum value of the lowest bin
   *           maxvalue: the maximum value of the highest bin
   *           bins : list of [min, max, count ],
   *           sparkline: url for sparkline histogram,
   *           histog: usr for full figure histogram
   */
  this.field_histograms = {};
  
  /**
   * query_terms: qt[<idx>] list of [field_name, low, high]    Default {}
   * query_terms are handled as "ANDed" together to form a single query
   *   if low = not present -> Invalid
   *   if low = "*" then unbounded lower limit:    field_name:[* TO high]
   *   if high = "*" then unbounded upper limit:   field_name:[low TO *]
   *   if high not present, then match query:      field_name:low
   * The query_terms element is set by the various selection mechanism available
   * on all pages.
   */
  this.query_terms = {};
 
  /**
   * The lucene version of the query defined by query_terms
   */
  this.raw_query_string = '*:*';
  
  /**
   * The optional filter query applied against all queries
   */
  this.filter_query = null;

  /**
   * A list of UI elements for editing the query (selectors, inputs)
   * Indexed by qid.
   */
  this.query_interfaces = {};
  
  /**
   * Adds a newly created UI to the list, or updates an existing one.
   */
  this.addInterface = function(qid, ui)
  {
    this.query_interfaces[qid] = ui;
  }

  
  this.destroyInterface = function(qid)
  {
    delete (this.query_interfaces[qid]);
  }
  
  /**
   * Returns the qid of the first UI component being used for editing name
   */
  this.getInterfaceByName = function(name)
  {
    for (var qid in this.query_interfaces)
    {
      this.log('getInterfaceByName: ' + name + ":" + qid);
      var ui = this.query_interfaces[qid];
      var msg = '';
      for (var k in ui)
      {
        msg += k + " ";
      }
      this.log(msg);
      if (ui.getName() == name)
        return qid; 
    }
    return null;
  }
  
  this.uiReady = function()
  {
    for (var qid in this.query_interfaces)
    {
      if (!this.query_interfaces[qid].isReady())
        return false;
    }
    return true;
  }
  

  /**
   * Returns the next QID value.
   */
  this.nextQID = function()
  {
    var qid = this.last_qid;
    this.last_qid += 1;
    return qid;
  }

  this.getFieldInfo = function(fname)
  {
    for (i=0; i < this.fields.length; i++)
    {
      if (this.fields[i].name == fname)
        return this.fields[i];
    }
    if ((fname == '*') || (fname == '__raw'))
    {
      var info = {name:'*', typecode:'r', unique_count:0, label: 'Any'}
      if (fname == '__raw')
        info.label = "Raw Query";
      return info;
    }
    return null;
  }
  
  
  this.flagBusy = function(doshow)
  {
    jQuery('.fn_working_tag').each(function()
    {
      if (!doshow)
        jQuery(this).hide();
      else
        jQuery(this).show();
    });
  }
  
  /**
   * Just updates the var and iterates through all nodes that have the 
   * class fn_matching_docs.
   * @param n
   * @return
   */
  this.setMatchingDocuments = function(n)
  {
    this.matching_documents = n;
    jQuery('.fn_matching_docs').each(function()
    {
      jQuery(this).text(n.toString());
    });
  }

  /**
  * Just updates the var.  Override this function to do other things
  * like update UI elements and so forth.
  * @param n
  * @return
  */
  this.setTotalDocuments = function(n)
  {
    this.total_documents = n;
    jQuery('.fn_total_docs').each(function()
    {
      jQuery(this).text(n.toString());
    });
  }
  
  
  
  /**
   * Generates a list of lucene style query fragments from a supplied qset.
   * @param qset
   * @return list of query fragments, join together with " AND " to get query.
   */
  this.generateQueryString = function()
  {
    var query = [];
    var qidx = 0;
    for (var qkey in this.query_terms)
    {
      var cq = this.query_terms[qkey];
      var qs = '';
      var fieldinfo = this.getFieldInfo(cq[0]);
      var qval = cq[1];
      if (qval != undefined)
      {
        if (cq[0] == '')
          qs = "*:";
        else
          qs = cq[0] + ":";
        var qval2 = cq[2];
        if (qval != '*')
        {
          qval = this.escapeQt(qval, fieldinfo.typecode);
        }
        if ((qval2 == undefined) || 
            (qval2 == '') ||
            (qval2 == null))
        {
          if ((qs == '*:') && (qval != '*'))
            qs = qval;
          else
            qs += qval;
        }
        else
        {
          if (qval2 != '*')
            qval2 = this.escapeQt(qval2);
          qs += "[" + qval + " TO ";
          qs += qval2 + "]";
        }
        query[query.length] = qs;
      }
    }
    return query;
  }

  /**
   * Updates the raw_query_string value to the current query set.
   * @param qset
   * @return The full query string with line breaks.
   */
  this.updateQueryString = function()
  {
    var qs = this.generateQueryString(this.query_terms);
    var q = '';
    var q2 = ''
    for (var i=0; i < qs.length; i++)
    {
      if (i == 0)
      {
        q = qs[i];
        q2 = qs[i];
      } 
      else
      {
        q = q + ' AND ' + qs[i];
        q2 = q2 + ' AND \n' + qs[i];
      }
    }
    this.raw_query_string = q;
    jQuery('.fn_raw_query_string').each(function()
    {
      jQuery(this).text( q );
    });
    return q2;
  }
  
  
  /**
   * Clear the query, set it to nothing.
   * @param updateqstring If true, then signal to update the qstring as well.
   */
  this.clearQuery = function(updateqstring)
  {
    this.query_terms = {};
    if (updateqstring)
      this.updateQueryString();
  }
  
  
  this.destroyQueryTerm = function(qid, updateqstring)
  {
    this.log('destroyQueryTerm:' + qid + ':' + updateqstring);
    delete (this.query_terms[qid]);
    this.destroyInterface(qid);
    if (updateqstring)
      this.updateQueryString();
    this.updateDataDisplay();
  }
  
  
  /**
   * This method is called when the query has changed.  Override it to be called
   * to update the grid or spatial or other rendering.
   * @return
   */
  this.updateDataDisplay = function()
  {
  }
  
  /**
   * Sets a query term, replacing existing entry for a field or adding a new one.
   * @param qid  The ID for the query term to manipulate
   * @param field The name of the field
   * @param v1  Lower value of query
   * @param v2  Optional upper value of query (use null if not setting)
   * @param updateqstring If evaluates true, then update the raw_query_string.
   * @param updatedisplay If true, then call updateDisplay, which typically 
   *      fires some requests to the server.
   * @return
   */
  this.setQueryTerm = function(qid, field, v1, v2, updateqstring, updatedisplay)
  {
    this.log('setQueryTerm: ' + field + ':' + v1 +':' + v2 + ':' + updateqstring + ':' + updatedisplay);
    if ((v1 == '') || (v1 == null) || (v1==undefined))
      this.destroyQueryTerm(qid, false);
    else
      this.query_terms[qid] = [field, v1, v2];
    if (updateqstring)
      this.updateQueryString();
    if (updatedisplay)
      this.updateDataDisplay();
  }

  
  /**
   * Returns a dictionary of terms that can be posted or added to a GET 
   * request based on the set of query_terms.  
   * 
   * @return dictionary of terms suitable for URL parameters.
   */
  this.generateQuery = function()
  {
     var query = {};
     var qidx = 0;
     for (var qkey in this.query_terms)
     {
       var qval = this.query_terms[qkey][1];
       if (qval != undefined)
       {
         query['qt'+qidx] = this.query_terms[qkey][0];
         query['qt'+qidx+'_min'] = this.query_terms[qkey][1];
         if ((this.query_terms[qkey][2] != undefined) && 
             (this.query_terms[qkey][2] != ''))
           query['qt'+qidx+'_max'] = this.query_terms[qkey][2];
         qidx += 1;
       }
     }
     var qdebug = '';
     for (k in query)
       qdebug += k + '=' + query[k] + "\n";
     jQuery('.fn_debug_queryterms').text(qdebug);
     return query;
  }

  
  /**
   * Executes an ajax request to retrieve the number of matching and total 
   * documents.
   * @param qset
   *    The query set to be executed
   * @param clearsession
   *    If true then indicate that session state should be updated (default)
   * @return nothing
   */ 
  this.matchCount = function(clearsession, dbg)
  {
    this.log('matchCount: ' + clearsession + ' ' + dbg);
    var params = this.generateQuery();
    if (!this.uiReady())
    {
      this.log('matchCount: ui not ready.');
      return;
    }
    if (this.pause_updates)
    {
      this.log('matchCount: updates paused.');
      return;
    }
    this.log('matchCount: ui ready.');
    //clearsession = 0;
    var url="/idx/__count/";
    if (clearsession == undefined)
      clearsession = 1;
    else if (clearsession != 0)
      clearsession = 1;
    else
      clearsession = 0;
    params['qtx'] = clearsession;
    if (dbg != undefined)
      params['dbg'] = dbg;
    else
      params['dbg'] = 'no.dbg';
    if ((this.filter_query != null) && (this.filter_query != ''))
      params['fq'] = core_fquery;
    jQuery.getJSON(url, params, _matchCount_callback);
  }
  
  
  /**
   * Executes the current qset with the action being target.  Note that
   * calling this method will cause a new page to be loaded in the browser.
   * @param target
   *   The target of the post.  Defaults to "."
   * @param oparams 
   *   optional additional parameters to send 
   * @return - generates a new document and replaces the current one.
   */
  this.executeQuery = function(target, oparams)
  {
    params = this.generateQuery();
    if (target == undefined)
      target = ".";
    var qform = jQuery('<form method="get" action="' + target +'" />').appendTo('body');
    for (var key in params)
    {
      var txt = '<input type="hidden" name="' + key + '" value="' + params[key] + '" />';
      var i = qform.append(txt);
    }
    if (oparams != undefined)
    {
      for (var key in oparams)
      {
        qform.append('<input type="hidden" name="' + key + '" value="' + oparams[key] + '" />');
      }
    }
    qform.submit();
  }
}


/**
 * Escape a lucene query term.
 * @param text Value to be escaped (will be converted to a string).
 * @return The escaped text.
 */
FishnetCore.prototype.escapeQt = function(text, tcode)
{
  text = text.toString();
  if (!arguments.callee.sRE)
  {
    if ((tcode == 'f') || (tcode =='i'))
    {
      var specials = ['+', '-', '!', '(', ')', '{', '}', '[', ']', '^', '"', 
                      '\\']; 
      arguments.callee.sRE = new RegExp(
          '(\\' + specials.join('|\\') + ')', 'g'
      );      
    }
    else
    {
      var specials = ['+', '-', '!', '(', ')', '{', '}', '[', ']', '^', '"', 
                      '~', '*', '?', ':', '\\']; 
      arguments.callee.sRE = new RegExp(
        '(\\' + specials.join('|\\') + ')', 'g'
      );
    }
  }
  var newtext = text.replace(arguments.callee.sRE, '\\$1');
  if ((tcode == 's') || (tcode == 't'))
  {
    if (newtext[newtext.length - 1] != "*")
      newtext = '"' + newtext + '"';
  }
  return newtext;   
}


////////////////////////////////////////////////////////////////////////////////
/**
 * Global variable providing state information for the web client.
 */
var fncore = new FishnetCore();


_matchCount_callback = function(data)
{
  //alert(JSON.stringify(data));
  fncore.setTotalDocuments(data.data.total_documents);
  fncore.setMatchingDocuments(data.data.num_matching);
}

////////////////////////////////////////////////////////////////////////////////


/**
 * Basic Search component implemented as a jQuery plugin.  
 * Configures and provides the actions of the basic search element.
 * Provide the id of the containing div element.  Expects that the select
 * entity is populated with options.  Each option has a nonstandard attribute of
 * "ftype" which indicates the type of the field and "fcount" which is the 
 * number of instances of that field. e.g.:
 * div id='basic_search'>
 *  select>
 *   option ftype='i' fcount='12' value='monthCollected_i'>Month Coll. /option>
 *  /select>
 * /div>
 *
 * @param _fncore An instance of FishnetCore()
 * @param target The destination of the generated form submit
 * @param add_match_count If true, then get the number of documents that match
 *   when the input value changes.
 * @param qid Identifier of this query term in the main query.
 */
jQuery.fn.FnBasicSearch = function(_fncore, options)
{
  var settings = jQuery.extend(
      {
        target: null,
        qid: _fncore.nextQID(),
        add_match_count: true,
        isReady: false,
        afterLoad: false
      }, options);
  var container = jQuery(this);
  var me = this;
  var input = null;
  var gobutton = null;
  
  this.getName = function()
  {
    return settings.name;
  }  
  
  this.isReady = function()
  {
    return settings.isReady;
  }  
  
  this.setupUI = function()
  {
    var row = jQuery("<tr id='fnentry_"+settings.qid+"'></tr>").appendTo(container);
    jQuery("<td align='left'>" + settings.label + "</td>").appendTo(row);
    var tcel = jQuery("<td align='left' colspan='3'></td>").appendTo(row);
    input = jQuery('<input size="50" type="text" value="" id="input_q_' + settings.qid + '" class="selecta_input"/>').appendTo(tcel);
    gobutton = jQuery("<input type='button' value='Update' />").appendTo(tcel);
    tcel = jQuery('<td id="delranger_' + settings.qid +'" ><span style="color:red;">X</span></td>').appendTo(row);
  }
  
  
  this.setupUI();
  _fncore.addInterface(settings.qid, this);
  
  
  gobutton.click(function()
  {
    var v1 = input.val();
    _fncore.setQueryTerm(settings.qid, settings.name, v1, null, true, true)
    if (settings.target == null)
      _fncore.matchCount(true,'gobutton.click');
    else
    {
      _fncore.executeQuery(settings.target, {});
    }
  });  
  
  
  jQuery('#delranger_' + settings.qid).click(function()
  {
    _fncore.destroyQueryTerm(settings.qid);
    jQuery('#fnentry_' + settings.qid).remove();
  });
  
  
  this.make_autofillUrl = function(ourl,q,extraParams)
  {
     q = settings.name + ":" + _fncore.escapeQt(q) + "*";
     var url = ourl + "?q=" + encodeURIComponent(q);
     for (var i in extraParams) {
       url += "&" + i + "=" + encodeURIComponent(extraParams[i]);
     }
     return url; 
  }
  
  
  this.onItemSelect = function(li)
  {
    var v1 = input.val();
    if ((v1 == '') || (v1 == null) || (v1 == undefined))
    {
      _fncore.destroyQueryTerm(self.qid, true);
    }
    else
      _fncore.setQueryTerm(settings.qid, settings.name, v1, null, true, true);
    _fncore.matchCount(true, 'onItemSelect'); // ??
  }
  
  this.manualEntry = function(params)
  {
    jQuery('#input_q_' + settings.qid).val(params.v);
    _fncore.setQueryTerm(settings.qid, settings.name, params.v, null, false, false)
  }

  
  this.setup_autofill = function()
  {
    input.val('');
    input.attr("autocomplete", "off");    
    input.unbind('keydown');
    var url = '/idx/fields/' + settings.name + '/';
    opts = { minChars:1,
             autoFill:true,
             matchCase: true,
             extraParams:{nterms:10, astext:1},
             makeUrl: this.make_autofillUrl };
    if (settings.add_match_count)
      opts.onItemSelect = this.onItemSelect;
    input.autocomplete(url, opts)
    //_fncore.matchCount(true, 'setup_autofill'); // ??
  };
    
  this.setup_autofill();
  if (settings.afterLoad)
  {
    settings.afterLoad(settings.qid);
  }
  settings.isReady = true;
  return this;
}

////////////////////////////////////////////////////////////////////////////////
/** 
 * Plugin for a single selector.
 */
 //f_name = name of index field to use
jQuery.fn.SlideSelect = function(_fncore, fname, options)
{
   var settings = jQuery.extend(
     {
       qid: _fncore.nextQID(),
       name: fname,
       full_base: true,
       url: '/idx/fields/' + fname + '/histo/',
       nbins: 80,
       height: 30,
       width: 250,
       logscale: true,
       query: false,
       fquery: false,
       manual_values: false,
       afterLoad: false,
       thisobj: false,
       initialvalues: false,
       isReady: false
     },options);
   
   //_fncore.loadFieldHistogram(fname, settings);

   
   var slider = null;
   var selector = null;
   var container = jQuery(this);
   var me = this;
   settings.thisobj = this;
   container.addClass('fn_selector_generated_' + settings.qid );
   
   this.getName = function()
   {
     return settings.name;
   }

   this.isReady = function()
   {
     //alert('isReady:' + settings.isReady );
     return settings.isReady;
   }
   
   function getValues()
   {
     res = [];
     res[0] = jQuery('#minval_' + settings.qid).val();
     res[1] = jQuery('#maxval_' + settings.qid).val();
     return res
   }
   
   function updateValues(qid, minv, maxv)
   {
     jQuery('#minval_' + qid).val(minv);
     jQuery('#maxval_' + qid).val(maxv);
   }

   function updateQuery(qid, minv, maxv)
   {
      _fncore.log('slideSelect: updateQuery:' + qid + ':' + minv + ':' + maxv)
      if ((minv == undefined) && (maxv == undefined))
      {
        var tmp = this.getValues();
        minv = tmp[0];
        maxv = tmp[1];
      }
      var ismin = false;
      var ismax = false;
      var hdata = _fncore.field_histograms[fname];
      //_fncore.log(JSON.stringify(hdata));
      if (minv == hdata.minvalue)
      {
        ismin = true;
        minv = '*';
      }
      if (maxv == hdata.maxvalue)
      {
        ismax = true;
        maxv = '*';
      }
      if (!(ismin && ismax))
      {
        _fncore.log('updateQuery: ismin && ismax');
        _fncore.setQueryTerm(settings.qid, settings.name, minv, maxv, true, true);
      }
      else
      {
        _fncore.log('updateQuery: ! (ismin && ismax)');
        _fncore.destroyQueryTerm(settings.qid, true);
      }
   }

   this.setValue = function(params)
   {
     //find the index of the bin
     var i=0;
     var v = params.v;
     var found = false;
     var fname = settings.name;
     settings.manual_values = true;
     var binl = _fncore.field_histograms[fname].bins.length;
     if ((v < _fncore.field_histograms[fname].bins[0][0]) || (v == '*'))
       v = _fncore.field_histograms[fname].bins[0][0];
     else if ((v > _fncore.field_histograms[fname].bins[binl-1][1]) || (v == '*'))
       v = _fncore.field_histograms[fname].bins[binl-1][1];
     while ((!found) && (i < _fncore.field_histograms[fname].bins.length))
     {
       if (params.ismin)
       {
         if ((_fncore.field_histograms[fname].bins[i][0] <= v) && 
             (v <= _fncore.field_histograms[fname].bins[i][1]))
         {
           found = true;
           slider.slider('values',0,i);
           if (slider.slider('values',1) < i)
             slider.slider('values',1,i);
         }
       }
       else
       {
         if ((_fncore.field_histograms[fname].bins[binl-1-i][0] <= v) 
             && (v <= _fncore.field_histograms[fname].bins[binl-1-i][1]))
         {
           found = true;
           slider.slider('values',1,binl-1-i);
           if (slider.slider('values',0) > binl-1-i)
           { 
             slider.slider('values',0,binl-1-i);
           }
         }
       }
       i += 1;
     }
     return v;
   }
   
   this.manualEntry = function(params)
   {
     _fncore.log('SlideSelect:manualEntry:' + params.ismin + ':' + params.v + ':' + params.v2)
     var minv = null;
     var maxv = null;
     if (params.v2 == undefined)
     {
       if (params.ismin)
       {
         minv = params.v;
         minv = me.setValue({ismin:true, v:minv});
       }
       else
       {
         maxv = params.v;
         maxv = me.setValue({ismin:false, v:maxv});
       }
     }
     else
     {
       minv = params.v;
       maxv = params.v2;
       minv = me.setValue({ismin: true, v: minv});
       maxv = me.setValue({ismin: false, v: maxv})
     }
     
     //Signal that the value has changed
     if ((minv == null) || (maxv == null))
     {
       var tmp = getValues();
       minv = tmp[0];
       maxv = tmp[1];
     }
     updateValues(settings.qid, minv, maxv);
     updateQuery(settings.qid, minv, maxv);
   }

   function setupUI()
   {
     var hd = _fncore.field_histograms[fname];
     var slideoptions = {
            range: true,
            min:0,
            max: hd.bins.length-1,
            values: [0, hd.bins.length-1],
            slide: function(event, ui)
            {
              //minv and maxv are values of the histogram bins for the left of 
              //the min slider and right of the max slider
              settings.manual_values = false;
              var minv = hd.bins[ui.values[0]][0];
              var maxv = hd.bins[ui.values[1]][1];
              updateValues(settings.qid, minv, maxv);
            },
            stop: function(event, ui)
            {
              //update the query after the slider has stopped.
              //this will in turn trigger an update of the data on the page.
              settings.manual_values = false;
              var minv = hd.bins[ui.value[0]][0];
              var maxv = hd.bins[ui.value[1]][1];
              updateQuery(settings.qid, minv, maxv);
            }
          };
      selector = jQuery("<tr id='ranger_"+ settings.qid + "'/>").appendTo(container);
      selector.append("<td align='left'>" + hd.label + "</td>");
      selector.append("<td align='left'><input type='text' id='minval_" + settings.qid + "' class='selecta_input'></input></td>");
      var selinner = jQuery("<td align='left'/>").appendTo(selector);
      slider = jQuery('<div id="slide_'+settings.qid+'" class="selecta_slider"><img src="' + hd.sparkline +'" /></div>').appendTo(selinner);
      slider.slider(slideoptions);
      slider.css({border: 0, height: settings.height + 'px'});
      selector.append("<td align='left'><input type='text' id='maxval_" + settings.qid + "' class='selecta_input'></input></td>");
      selector.append("<td id='delranger_" + settings.qid +"' style='color:red;'>X</td>");
      var minv = hd.bins[slideoptions.values[0]][0];
      var maxv = hd.bins[slideoptions.values[1]][1];
      updateValues(settings.qid, minv, maxv);
  
      jQuery('#minval_' + settings.qid).change(function()
      {
        me.manualEntry({ ismin:true, v:jQuery(this).val() });
      });
      
      jQuery('#maxval_' + settings.qid).change(function()
      {
        me.manualEntry({ ismin:false, v:jQuery(this).val() });
      });
      
      jQuery('#delranger_' + settings.qid).click(function()
      {
        me.destroy();
      });
      
      _fncore.flagBusy(false);
      me.doAfterLoad();
   }

   this.destroy = function()
   {
     _fncore.log('SlideSelect:destroy');
     _fncore.destroyQueryTerm(settings.qid);
     slider.slider('destroy');
     jQuery('#ranger_' + settings.qid).remove();
   }
   
   
   this.loadFieldHistogram = function()
   {
     if (_fncore.field_histograms[settings.name] != undefined)
     {
       setupUI();
       return;
     }
     //Load the histogram data.
     _fncore.flagBusy(true);
     var url = settings.url + "?";
     url += '&nbins=' + encodeURI(settings.nbins);
     url += '&width=' + encodeURI(settings.width);
     url += '&height=' + encodeURI(settings.height);
     url += '&logscale=' + encodeURI(settings.logscale);
     if (settings.query)
       url += '&q=' + encodeURI(settings.query);
     if (settings.fquery)
       url += '&fq=' + encodeURI(settings.fquery);
     jQuery.getJSON(url, function(data)
       {
         _fncore.field_histograms[settings.name] = {
           label:data.data[settings.name].label,
           minvalue:data.data[settings.name].minvalue,
           maxvalue:data.data[settings.name].maxvalue,
           bins:data.data[settings.name].hdata,
           sparkline:data.data[settings.name].sparkline_url,
           histog:data.data[settings.name].histogram_url
         };
         setupUI();
       });
   }
   
   this.doAfterLoad = function()
   {
     if (settings.initialvalues)
     {
       me.manualEntry({ ismin:true, 
                        v:settings.initialvalues[0],
                        v2:settings.initialvalues[1] });
     }
     //settings.manual_values = false;
     //alert('afterload, isready:' + settings.isReady);
     settings.isReady = true;
     if (settings.afterLoad)
       settings.afterLoad(settings.qid);
   }
   
   this.loadFieldHistogram();
   _fncore.addInterface(settings.qid, me);
   return this;
   return jQuery(this);
}

/**
 * Given a list of [field_name, minv, maxv], generate the user interface.
 * @param qterms
 * @return
 */
function createUiForExistingQuery(qterms, onloadFunc)
{
  if (onloadFunc == undefined)
    onloadFunc = false;
  for (var i=0; i < qterms.length; i++)
  {
    var fieldinfo = fncore.getFieldInfo(qterms[i][0]);
    if (fieldinfo)
    {
       var fieldtype = fieldinfo.typecode;
       var ui = null;
       if ((fieldtype == 'f') || (fieldtype=='i'))
       {
         ui = jQuery('#fn_query_builder').SlideSelect(fncore, 
                fieldinfo.name,
                { initialvalues: [qterms[i][1], qterms[i][2]],
                  afterLoad: onloadFunc} );
       }
       else
       {
         fieldinfo.afterLoad = onloadFunc; 
         ui = jQuery('#fn_query_builder').FnBasicSearch(fncore, fieldinfo);
         ui.manualEntry({ v:qterms[i][1]});
       }
    }
  }
}
 

function syncQueryUI(qterms)
{
  return;
  for (var i=0; i < qterms.length; i++)
  {
    var fieldinfo = fncore.getFieldInfo(qterms[i][0]);
    var ui = fncore.getInterfaceByName(fieldinfo.name);
    if ((ui != undefined) && (ui != null))
    {
      if (qterms[i][2] != undefined)
        ui.manualEntry({ ismin: false, v:qterms[i][2] });
      else
        ui.manualEntry({ ismin: true, v:qterms[i][1] });
    }
  }  
}



                    