
if (! window.filters) { var filters = {}; }


filters.FilterSet = function (opts) {
  /* CONSTRUCTOR.

    Required params:

      * data_url : the base url to call for reports.
      * url_init_callback : a function to call with the URL once
                            we have appended slider parameters to it.

      * sliders : the set of sliders to wrap and operate on.
      * inputs : the set of inputs to wrap.
      * dropdowns : the set of dropdowns to wrap.
        (All three of these map dom_id: uri_query_param_name
        for the objects they wrap.)

      * slider_precisions : maps slider dom ids to steps (ie. the finest
        grained unit a slider can move by, eg. 0.01).  Sliders 
        which do not set an explicit default use 1.
  */
    var defaults = {
        'data_url': 'REQUIRED',
        'url_init_callback': 'REQUIRED',
        'sliders': {},
        'slider_precisions': {},
        'inputs': {},
        'dropdowns': {}
    };
    $j.params(this, defaults, opts || {});
    this._join_char = (this.data_url.indexOf('?') > 0) ? '&' : '?';
    this.slider_initial_vals = {};
    this._filters_applied = false;
};

filters.FilterSet.prototype.submit_filters_faux_form = function() {
   /* Attached to the "FilterSet" button on the report, it (asynchronously) checks
      whether the submitted filters are valid (i.e. return > 0 records) and:
     * if they are: create the summary & update the grid
     * if they are not valid: (jquery-ui) popup message to notify the user, and
     do not change the grid
   */
    // show the spinning wheel while we do our thing
    $j('div#rcontent-wait').show()
   
    // asynchronously query for the json summary of the report
    $j.getJSON(this._get_filtered_data_url() + '&format=json', 
        $j.scope(this, '_handle_json'));

    // mark every (potentially) selected preset as deselected
    this._deselect_presets();
    
    // mark the undo button next to the filter-submit as "active"
    $j('#sliders-button-undo').addClass('active')
    return;
};

filters.FilterSet.prototype.bind_callbacks = function(button_selector, undo_selector) {
  /* Initializes the sliders on the page and also binds the
     button(s) used to initiate their filtering behaviour.
  */
    $j(button_selector).click($j.scope(this, 'submit_filters_faux_form'));
    $j(undo_selector).click($j.scope(this, 'undo_filters'));
    $j.each(this.sliders, $j.scope(this, '_initialize_slider'));
};

filters.FilterSet.prototype.undo_filters = function(jevent, skip_reload) {
  /* Attached to the *undo* button on the client page, it moves the
     filters sliders to its original position, checks the boxes, resets the
     category, removes all visual indicators of filtered data and reloads the
     original grid
  */
    var set_slider = $j.scope(this, '_set_slider');
 
    if (this._filters_applied) {
        $j('div#rcontent-wait').show() // the spinning wheel
    }

    $j('#filtered-summary').remove();
    $j('#unfiltered-summary').show();
 
    // Reset the checkboxes to checked.
    $j.each(this.inputs, function (iid, _) {
        $j("input[name='" + iid + "']").attr('checked', true);
    });
   
    // Reset each slider individually
    $j.each(this.slider_initial_vals, function(key, value) {
        set_slider(key, value[0], value[1]);
    });
   
    // Reset the dropdowns
    $j.each(this.dropdowns, function (did, _) {
        cat_dropdown.clear(did, false);
    });

    // reload the grid if we've filtered the dataset.
    if (!skip_reload && this._filters_applied) {
        var url = this.data_url + (this.data_url.indexOf('?') > 0 ? '&' : '?') + 'undo_filters=1';
        this.url_init_callback(url);
        this._filters_applied = false;
    }
    // remove .selected classes from presets
    this._deselect_presets()
    $j('#sliders-button-undo').removeClass('active')
    
    return false;
};

filters.FilterSet.prototype.bind_presets = function(presets_container_id) {
  /* Given the id of the element that wraps the presets definitions, it 
     traverses the dom to find all elements w/ a class of .preset, and adds
     a click callback containing _filtering_ functionality to each one.
  */
    var self = this,
        // the presets objects
        presets = $j('#' + presets_container_id + ' .preset');
        // the divs wrapping each preset object (basically, preset.parent())
        preset_wraps = $j('#' + presets_container_id + ' .preset-wrap')
  
    presets.click(function() {
        var this_ = $j(this)
        var type_ = this_.attr('id').slice(0,8),
            preset_name = this_.attr('id').slice(8),
            url = self.data_url.slice(-1) == '/' ? self.data_url + '?' : self.data_url,
            preset_url = url + '&format=json_preset&preset_name=' + preset_name;
        
        // mark the preset as loading (puts a spinning wheel up)
        this_.addClass('loading')
        // request the preset url, and do <callback> when you get it back
        $j.getJSON(preset_url, function(preset_def) {
            // this most certainly *should not* be an anonymous function --
            // there should be a this._handle_json_presets, or something to
            // that extent. This is one of the many corners we are cutting to
            // make the Q3_2009 release possible
            
            // presets are named preset__filter_name
            if (type_ != 'preset__' || !preset_def) {return false;}
                        
            // start by undoing the already existing filters        
            self.undo_filters(undefined, true);
                
            // set the filters now (the sliders, and widgets, and checkboxes)
            $j.each(preset_def, function(key, value) {
                // set the category
                // TODO: the widget's object (cat_dropdown) shouldn't be hardcoded
                // the comment above turned out to be prophetic (look below)
                if (key == 'category') {
                  cat_dropdown.set_category_id('categories-dropdown', value);
                }
                // the category widget dropdown is binded to a different name
                // I wish Roy would pay attention to conventions
                if (key == 'category_search') {
                  cat_dropdown.set_category_id('cat_filter_dropdown', value);
                }
                // set the sliders
                else if (key.slice(0, 6) == 'slider') {
                  if (value[0] != null) {self._set_slider_min(key, value[0]); }
                  if (value[1] != null) {self._set_slider_max(key, value[1]); }
                }
                // set the special case checkboxes
                else if (key.slice(0, 3) == 'chk') {
                  $j("input[name='" + key.slice(4) + "']").attr('checked', value);
                }
            });
            this_.removeClass('loading');
            self.submit_filters_faux_form();
            // mark the filter as selected
            this_.parent().addClass('selected')
        });
    });
}

filters.FilterSet.prototype._deselect_presets = function () {
    /* Remove the .selected class from each preset object */
    $j('.preset-wrap').each(function() {$j(this).removeClass('selected')})
}

filters.FilterSet.prototype._handle_json = function(json) {
  /* Callback handler: This is run when our JSON data returns from checking
     the server to see if a particular set of filters will return some data or not.
  */
    var undo_cb = $j.scope(this, 'undo_filters');
    if (json.dataset_count == 0) {
        // the user filtered itself to an empty dataset...
        var msg = '<div class="dialog-search"><p>Sorry, nothing matches your selected filters.</p><p>Please close this message window to revert back to your last filter settings.</p></div>';
        _show_dialog(msg, 'warning', 
            {'close': function() { $j('div#rcontent-wait').hide(); }});
    }
    else {
        // hide the unfiltered-summary, and put a filtered-summary in its place
        $j('#unfiltered-summary').hide();
        $j('#filtered-summary').remove(); // user filters for a second time
        
        // the filter is valid -- do the grid and make up a summary
        var url = this._get_filtered_data_url(); 
        var csv = $j('<a id="csv-export"><div></div>Export Table to CSV</a>').attr('href', url + '&format=csv');
        
        $j('#table-summary').append(
            $j('<div id="filtered-summary"></div>').
            append($j('<div class="undo"><div></div>Unfilter Results</div>').bind('click', undo_cb)).
            append(csv).
            append($j('<div></div>').addClass('dataset-count').
                html('Now displaying ' + json.dataset_count + ' / ' + json.unfiltered_count)
            )
        );
        this.url_init_callback(url);
        this._filters_applied = true;
    }
};

filters.FilterSet.prototype._get_filters_url_parameters = function() {
  /* Returns the filters as a GET string (i.e. s1_ref=25,50&s2_ref...), ready 
     to be plopped into a url
  */
    var value, url = [];
  
    $j.each(this.sliders, function (sid, uri_key) {
      value = $j('#' + sid + ' .min').html() 
        + ',' 
        + $j('#' + sid + ' .max').html();
      url.push(uri_key + '=' + value);
    });
 
    $j.each(this.dropdowns, function (did, uri_key) {
        value = cat_dropdown.get_category_id(did);
        if (value && value > 0) {
            url.push(uri_key + '=' + value);
        }
    });

    $j.each(this.inputs, function (iid, uri_key) {
      value = $j("input[name='" + iid + "']").attr('checked');
      url.push(uri_key + '=' + value);
    });
  
    return url.join('&');
};

filters.FilterSet.prototype._get_filtered_data_url = function () {
  /* Return the fully formatted filtered url for this FilterSet. */ 
    return this.data_url + this._join_char + this._get_filters_url_parameters();
};

filters.FilterSet.prototype._initialize_slider = function(slider_id) {
  /* Initializes a slider given its dom id and an array pair of its 
     [lower, upper] bounds. 
  */
    var imin = Number($j('#' + slider_id + ' .min').html()),
        imax = Number($j('#' + slider_id + ' .max').html()),
        precision = this.slider_precisions[slider_id] || 0,
        step = Math.pow(10, -1 * precision);
  
    this.slider_initial_vals[slider_id] = [imin, imax];
 
    if (imin == imax) {
        // Max = min, ie. there is nothing to filter on, so hide the filter.
        $j('#' + slider_id).hide();
        $j('#' + slider_id + ' + .separator').hide();
    } else {
        $j('#' + slider_id + ' > .slider-widget').slider({
            'range': true,
            'min': imin,
            'max': imax,
            'values': [imin, imax],
            'step': step,
            'slide': function(e, ui) {
                $j('#' + slider_id + ' .min').html(Number(ui.values[0]).toFixed(precision));
                $j('#' + slider_id + ' .max').html(Number(ui.values[1]).toFixed(precision));
            }
        });
    }
};  

filters.FilterSet.prototype._set_slider = function(slider_id, min, max) {
  /* Sets a slider (and the accompanying html blocks) to the given min & max
     values
  */
    var slider = $j('#' + slider_id + ' > .slider-widget');
    if (slider.length > 0) {
      this._set_slider_min(slider_id, min);
      this._set_slider_max(slider_id, max)
    }
};

filters.FilterSet.prototype._set_slider_min = function(slider_id, min) {
  var slider = $j('#' + slider_id + ' > .slider-widget');
  if (slider.length > 0) {
      $j('#' + slider_id + ' .min').html(min); // the html
      slider.slider('values', 0, min); // the widget
  }  
}  

filters.FilterSet.prototype._set_slider_max = function(slider_id, max) {
  var slider = $j('#' + slider_id + ' > .slider-widget');
  if (slider.length > 0) {
      $j('#' + slider_id + ' .max').html(max); // the html
      slider.slider('values', 1, max); // the widget
  }  
}
  
