
// {{{ base64 strings
var base64String = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
var base64Pad = '=';

var base64Replacements = [
  ['a', '('],
  ['b', ')'],
  ['c', '{'],
  ['+', 'a'],
  ['/', 'b'],
  ['=', 'c'],
  ['(', '*'],
  [')', '-'],
  ['{', '~']
];

for (var i = 0; i < base64Replacements.length; i++) {
  base64String = base64String.replace(base64Replacements[i][0], base64Replacements[i][1]);
  base64Pad = base64Pad.replace(base64Replacements[i][0], base64Replacements[i][1]);
}
// }}} base64 strings

var FilterSelectorResults = Class.create();


FilterSelectorResults.prototype = {
  filter: "",
  searchTerm: "",
  resultsUrl: "",
  realObjectID: "",
  resultsElement: "",
  currentHistory: "",
  
  // {{{ _toBase64(s)
  _toBase64: function(s) {
    // the result/encrypted string, the padding string, and the pad count
    var base64chars = base64String.split("");
    
    if (!s) {
      s = "";
    }
    
    var r = "";
    var p = "";
    var c = s.length % 3;
    
    // add a right zero pad to make this string a multiple of 3 characters
    if (c > 0) { for (; c < 3; c++) { p += base64Pad; s += "\0"; } }
    
    // increment over the length of the string, three characters at a time
    for (c = 0; c < s.length; c += 3) {
      
      // we add newlines after every 76 output characters, according to the MIME specs
      //if (c > 0 && (c / 3 * 4) % 76 == 0) { r += "\r\n"; }
      
      // these three 8-bit (ASCII) characters become one 24-bit number
      var n = (s.charCodeAt(c) << 16) + (s.charCodeAt(c+1) << 8) + s.charCodeAt(c+2);
      
      // this 24-bit number gets separated into four 6-bit numbers
      n = [(n >>> 18) & 63, (n >>> 12) & 63, (n >>> 6) & 63, n & 63];
      
      // those four 6-bit numbers are used as indices into the base64 character list
      r += base64chars[n[0]] + base64chars[n[1]] + base64chars[n[2]] + base64chars[n[3]];
    }
    
    // add the actual padding string, after removing the zero pad
    return r.substring(0, r.length - p.length) + p;
 },
  // }}} _toBase64(s)
  
  // {{{ _fromBase64(s)
  _fromBase64: function(s) {
    if (!s) {
      s = "";
    }
    
    // replace any incoming padding with a zero pad (the 'A' character is zero)
    var p = (s.charAt(s.length-1) == base64Pad ? (s.charAt(s.length-2) == base64Pad ? 'AA' : 'A') : "");
    var r = "";
    s = s.substr(0, s.length - p.length) + p;
    
    var base64chars = base64String.split("");
    var base64inv = {};
    for (var i = 0; i < base64chars.length; i++) {
      base64inv[base64chars[i]] = i;
    }
    
    // remove/ignore any characters not in the base64 characters list -- particularly newlines
    //s = s.replace(new RegExp('[^'+base64String.join("")+']', 'g'), "");
    
    // increment over the length of this encrypted string, four characters at a time
    for (var c = 0; c < s.length; c += 4) {
      
      // each of these four characters represents a 6-bit index in the base64 characters list
      //  which, when concatenated, will give the 24-bit number for the original 3 characters
      var n = (base64inv[s.charAt(c)] << 18) + base64inv[s.charAt(c+3)] +
       (base64inv[s.charAt(c+1)] << 12) + (base64inv[s.charAt(c+2)] << 6);
      
      // split the 24-bit number into the original three 8-bit (ASCII) characters
      r += String.fromCharCode((n >>> 16) & 255, (n >>> 8) & 255, n & 255);
    }
    
    // remove any zero pad that was added to make this a multiple of 24 bits
    return r.substring(0, r.length - p.length);
  },
  // }}} _fromBase64(s)
  
  // {{{ _decodeHashHistoryComponents(historyToken)
  _decodeHashHistoryComponents: function(historyToken) {
    if (-1 < historyToken.search(/(.*?)_(\d+)_(.*?)_(.*?)/)) {
      var aHistoryComponents = historyToken.replace(/(.*?)_(\d+)_(.*?)_(.*?)/, '$1___$2___$3___$4').split('___');
      aHistoryComponents[2] = this._fromBase64(aHistoryComponents[2]);
      return aHistoryComponents;
    }
    
    return null;
  },
  // }}} _decodeHashHistoryComponents(historyToken)
  
  // {{{ _encodeHashHistoryComponents(encodedAppliedFilters, pg, q, filterTypeState)
  _encodeHashHistoryComponents: function(encodedAppliedFilters, pg, q, filterTypeState) {
    return encodedAppliedFilters + '_' + pg + '_' + this._toBase64(q) + '_' + filterTypeState;
  },
  // }}} _encodeHashHistoryComponents(encodedAppliedFilters, pg, q, filterTypeState)
  
  // {{{ makeHistory(encodedAppliedFilters, pg, q, filterTypeState)
  makeHistory: function(encodedAppliedFilters, pg, q, filterTypeState) {
    var sHash = this._encodeHashHistoryComponents(encodedAppliedFilters, pg, q, filterTypeState);
    
    if (sHash != this.currentHistory) {
      //update the currentHistory marker so the polling mechanism
      //doesn't think it needs to refresh the filters
      this.currentHistory = sHash;
      
      $('currentHistoryField').onclick(this.currentHistory);
    }
  },
  // }}} makeHistory(encodedAppliedFilters, pg, q, filterTypeState)
  
  // {{{ setResultsLoading()
  setResultsLoading: function() {
    this.resultsElement.update('Loading...');
  },
  // }}} setResultsLoading()
  
  // {{{ encodeOpenedFilterTypes()
  encodeOpenedFilterTypes: function() {
    var openedFiltersArray = this.getOpenedFiltersArray();
    var oOpenedFilterTypeBits = new Hash();
    
    if (openedFiltersArray.length) {
      oOpenedFilterTypeBits.set(1, this.getOpenedFiltersArray());
      return this.filter._encodeAppliedFilters(oOpenedFilterTypeBits); 
    }
    
    return "";
  },
  // }}} encodeOpenedFilterTypes()
  
  // {{{ goResultsPage(pg)
  goResultsPage: function(pg) {
    this.setResultsLoading();
    
    var thisthis = this;
    var encodedAppliedFilters = this.filter.encodeAppliedFilters();
    
    new Ajax.Updater(this.resultsElement.id, this.resultsUrl, {
        parameters: {
          q: this.searchTerm,
          f: encodedAppliedFilters,
          pg: pg,
          realObjectID: this.realObjectID
        },
        method: 'get',
        onComplete: function() {
          thisthis.updatePaginationLinks();
        }
    });
    
    this.makeHistory(encodedAppliedFilters, pg, this.searchTerm, this.encodeOpenedFilterTypes());
  },
  // }}} goResultsPage(pg)
  
  // {{{ updatePaginationLinks()
  updatePaginationLinks: function() {
    var aHrefs = $$('div.pagination a');
    var pg = 0;
    
    var thisthis = this;
    
    for (var i = 0; i < aHrefs.length; i++) {
      aHrefs[i].onclick = function() {
        thisthis.goResultsPage( this.href.match(/[&|\?]pg=([^&]+)/)[1] );
        return false;
      };
    }
    
    if (this.highlightResults && this.searchTerm.length) {
      searchHighlightNode(this.resultsElement, this.searchTerm);
    }
    
  },
  // }}} updatePaginationLinks()
  
  // {{{ resetViewAlls()
  resetViewAlls: function() {
    var aViewAlls = $$('input.filtertype-viewall');
    for (var i = 0; i < aViewAlls.length; i++) {
      var filterTypeID = aViewAlls[i].id.split('-')[1];
      var allSelected = this.filter.allSelected(filterTypeID);
      var span = $$('h2#filterheader-' + filterTypeID + ' span')[0];
      var label = $('filtertype-' + filterTypeID + '-viewall-label');
      
      if (allSelected) {
        aViewAlls[i].checked = true;
        label.addClassName('checked');
        
        span.removeClassName('filtered');
        span.addClassName('unfiltered');
        
      } else {
        aViewAlls[i].checked = false;
        label.removeClassName('checked');
        
        span.removeClassName('unfiltered');
        span.addClassName('filtered');
      }
      
      
    }
  },
  // }}} resetViewAlls()
  
  // {{{ searchFormSubmit()
  searchFormSubmit: function() {
     this.searchTerm = $F('filter-q');
     
     // {{{ used by search term highlighter
     document.cookie = "fsq=" + encodeURIComponent(this.searchTerm);
     // }}} used by search term highlighter
     
     this.goResultsPage(1);
     return false;
  },
  // }}} searchFormSubmit()
  
  // {{{ initialize() (constructor)
  initialize: function(resultsUrl, resultsElement, realObjectID, highlightResults) {
    var thisthis = this;
    
    this.resultsUrl = resultsUrl;
    this.resultsElement = resultsElement;
    this.realObjectID = realObjectID;
    if (highlightResults) {
      this.highlightResults = true;
    } else {
      this.highlightResults = false;
    }
    
    this.filter = new FilterUtility();
    this.filter.addListener("updateResults", [this, 'updateResults']);
    this.filter.addListener("itemOnClick", [this, 'itemOnClick']);
    
    //this.makeHistory(this.filter.encodeAppliedFilters(), 1, this.encodeOpenedFilterTypes());
    //this.currentHistory = this._encodeHashHistoryComponents(this.filter.encodeAppliedFilters(), 1, this.encodeOpenedFilterTypes())
    
    this.updatePaginationLinks();
    
    $('searchForm').onsubmit = function() {
      return thisthis.searchFormSubmit();
    }
    this.searchTerm = $F('filter-q');
    
    var aViewAlls = $$('input.filtertype-viewall');
    var filter = this.filter;
    
    // {{{ initialize viewAlls, add onclick functions
    for (var i = 0; i < aViewAlls.length; i++) {
      aViewAlls[i].onclick = function() {
        var filterTypeID = this.id.split('-')[1];
        var span = $$('h2#filterheader-' + filterTypeID + ' span')[0];
        var label = $('filtertype-' + filterTypeID + '-viewall-label');
        
        span.removeClassName('unfiltered');
        span.addClassName('filtered');
        label.removeClassName('checked');
        
        if (this.checked) {
          filter.clearFilters(filterTypeID);
          label.addClassName('checked');
          
          span.removeClassName('filtered');
          span.addClassName('unfiltered');
          
        } else {
          if (filter.allSelected(filterTypeID)) {
            this.checked = true;
            label.addClassName('checked');
            
            span.removeClassName('filtered');
            span.addClassName('unfiltered');
          }
        }
      };
    }
    // }}} initialize viewAlls, add onclick functions
    
    // {{{ setup communication pipe with TiJsHistory
    new PeriodicalExecuter(
      function(pe) {
        if ($('currentHistoryField') && !$('currentHistoryField').ondblclick) {
          $('currentHistoryField').ondblclick = function(historyToken) {
            thisthis.recallHistory(historyToken);
          };
          
          if (thisthis._decodeHashHistoryComponents(location.hash.replace('#', ''))) {
            //There is an initial history, handle it
            //alert("recalling..." + location.hash.replace('#', ''));
            thisthis.recallHistory(location.hash.replace('#', ''));
          } else {
            //There is no initial history, create one and set it
            thisthis.makeHistory(thisthis.filter.encodeAppliedFilters(), 1, thisthis.searchTerm, thisthis.encodeOpenedFilterTypes());
            thisthis.currentHistory = '';
            //alert("setting initial..." + thisthis._encodeHashHistoryComponents(thisthis.filter.encodeAppliedFilters(), 1, thisthis.searchTerm, thisthis.encodeOpenedFilterTypes()));
            thisthis.recallHistory(thisthis._encodeHashHistoryComponents(thisthis.filter.encodeAppliedFilters(), 1, thisthis.searchTerm, thisthis.encodeOpenedFilterTypes()));
          }
          
          pe.stop();
        }
      },
      0.2
    );
    // }}} setup communication pipe with TiJsHistory
    
    // {{{ setup filter-block-headers
    var aFilterBlockHeaders = $$('h2.filter-block-header');
    for (var i = 0; i < aFilterBlockHeaders.length; i++) {
      aFilterBlockHeaders[i].onclick = function() {
        var filterBlock = $('filter-' + this.id.replace(/^filterheader-/, ''));
        filterBlock.toggle();
        
        if (filterBlock.getStyle("display") == "none") {
          this.removeClassName("filtershown");
          this.addClassName("filterhidden");
        } else {
          this.removeClassName("filterhidden");
          this.addClassName("filtershown");
        }
        return false;
      };
      
      aFilterBlockHeaders[i].insert({bottom: '<span class="unfiltered">(filtered)</span>'});
      
    }
    // }}} setup filter-block-headers
    
    this.resetViewAlls();
    
    // {{{ resize checkbox lists
    var aLists = $$('div.filter-block ul.filtertype.checkboxes');
    for (var i = 0; i < aLists.length; i++) {
      var aItems = $$('ul#' + aLists[i].id + ' li');
      if (aItems.length < 14) {
        //resize div/ul to aItems.length*18px height
        aLists[i].setStyle({height: Math.ceil(aItems.length * 28) + "px"});
        aLists[i].up().setStyle({height: Math.ceil(aItems.length * 28 + 2) + "px"});
      }
    }
    // }}} resize checkbox lists
    
    // {{{ setup li onclicks
    aItems = $$('div.filter-block ul.filtertype.checkboxes input[type="checkbox"]');
    for (var i = 0; i < aItems.length; i++) {
      var li = aItems[i].up();
      
      li.onclick = function(e) {
        var filterTypeID = this.id.split('-')[2];
        var bitNumber = this.id.split('-')[3];
        
        var cb = $('filteritem-' + filterTypeID + '-' + bitNumber);
        
        if (cb.checked) {
          cb.checked = false;
        } else {
          cb.checked = true;
        }
        
        cb.onclick();
        
        if (e) {
          if (e.preventDefault) {
            e.preventDefault();
          } else {
            e.returnValue = false;
          }
          if (e.stopPropagation) {
            e.stopPropagation();
          }
        }
        return false;
      };
      
      aItems[i].onfocus = function() {
        this.up().addClassName('hover');
      };
      
      aItems[i].onblur = function() {
        this.up().removeClassName('hover');
      }
    }
    // }}} setup li onclicks
  },
  // }}} initialize() (constructor)
  
  // {{{ loadResults(encodedAppliedFilters, pg)
  loadResults: function(encodedAppliedFilters, pg) {
    this.setResultsLoading();
    
    //alert("loadResults..." + this._encodeHashHistoryComponents(encodedAppliedFilters, pg, this.searchTerm, this.encodeOpenedFilterTypes()));
    
    var thisthis = this;
    
    new Ajax.Updater(this.resultsElement.id, this.resultsUrl, {
        parameters: {
          q: this.searchTerm,
          f: encodedAppliedFilters,
          realObjectID: this.realObjectID,
          pg: pg
        },
        method: 'get',
        onComplete: function() {
          thisthis.updatePaginationLinks();
        }
    });
    
    this.makeHistory(encodedAppliedFilters, pg, this.searchTerm, this.encodeOpenedFilterTypes());
  },
  // }}} loadResults(encodedAppliedFilters, pg)
  
  // {{{ recallHistory(historyToken)
  recallHistory: function(historyToken) {
    //alert("recallHistory..." + historyToken);
    var aHistoryComponents = this._decodeHashHistoryComponents(historyToken);
    if (aHistoryComponents && historyToken != this.currentHistory) {
      this.currentHistory = historyToken;
      
      this.filter.clearAppliedFilters();
      this.filter.addEncodedAppliedFilters(aHistoryComponents[0]);
      
      $('filter-q').value = this.searchTerm = aHistoryComponents[2];
      
      this.resetViewAlls();
      
      this.closeOpenedFilterTypes();
      this.addEncodedOpenedFilterTypes(aHistoryComponents[3]);
      
      //alert("recalled history: f=" + aHistoryComponents[0] + ", pg=" + aHistoryComponents[1]);
      this.loadResults(aHistoryComponents[0], aHistoryComponents[1]);
    }
  },
  // }}} recallHistory(historyToken)
  
  // {{{ updateResults(encodedAppliedFilters)
  updateResults: function(encodedAppliedFilters) {
    this.loadResults(encodedAppliedFilters, 1);
  },
  // }}} updateResults(encodedAppliedFilters)
  
  // {{{ newSearch(sTerm)
  newSearch: function(sTerm) {
    this.searchTerm = sTerm;
    this.updateResults(this.filter.encodeAppliedFilters());
    return false;
  },
  // }}} newSearch(sTerm)
  
  // {{{ itemOnClick(item)
  itemOnClick: function(item) {
    var filterTypeID = item.id.split('-')[1];
    
    var viewAll = $('filtertype-' + filterTypeID + '-viewall');
    
    if (viewAll) {
      var allSelected = this.filter.allSelected(filterTypeID);
      var span = $$('h2#filterheader-' + filterTypeID + ' span')[0];
      var label = $('filtertype-' + filterTypeID + '-viewall-label');
      
      if (allSelected) {
        this.filter.clearFilters(filterTypeID);
        viewAll.checked = true;
        label.addClassName('checked');
        
        span.removeClassName('filtered');
        span.addClassName('unfiltered');
        
      } else {
        viewAll.checked = false;
        label.removeClassName('checked');
        
        span.removeClassName('unfiltered');
        span.addClassName('filtered');
      }
    }
  },
  // }}} itemOnClick(item)
  
  
  
  
  
  // {{{ getOpenedFiltersArray()
  getOpenedFiltersArray: function() {
    var aFilterBlockHeaders = $$('h2.filter-block-header');
    var aReturn = new Array();
    
    for (var i = 0; i < aFilterBlockHeaders.length; i++) {
      var filterBlock = $('filter-' + aFilterBlockHeaders[i].id.replace(/^filterheader-/, ''));
      if (filterBlock.getStyle("display") != "none") {
        aReturn.push(filterBlock.id.split('-')[1]);
      }
    }
    
    return aReturn;
  },
  // }}} getOpenedFiltersArray()
  
  // {{{ closeOpenedFilterTypes()
  closeOpenedFilterTypes: function() {
    var aFilterBlockHeaders = $$('h2.filter-block-header');
    
    for (var i = 0; i < aFilterBlockHeaders.length; i++) {
      var filterBlock = $('filter-' + aFilterBlockHeaders[i].id.replace(/^filterheader-/, ''));
      if (filterBlock.getStyle("display") != "none") {
        filterBlock.toggle();
        aFilterBlockHeaders[i].removeClassName("filtershown");
        aFilterBlockHeaders[i].addClassName("filterhidden");
      }
    }
  },
  // }}} closeOpenedFilterTypes()
  
  // {{{ addEncodedOpenedFilterTypes(sEncodedFilterTypes)
  addEncodedOpenedFilterTypes: function(sEncodedFilterTypes) {
    var oOpenedFilterTypeBits = this.filter.decodeAppliedFilters(sEncodedFilterTypes);
    var aFilterTypes = oOpenedFilterTypeBits.get(1);
    
    if (aFilterTypes) {
      for (var i = 0; i < aFilterTypes.length; i++) {
        var filterBlock = $('filter-' + aFilterTypes[i]);
        var filterBlockHeader = $('filterheader-' + aFilterTypes[i]);
        
        if (filterBlock.getStyle("display") == "none") {
          filterBlock.toggle();
          filterBlockHeader.removeClassName("filterhidden");
          filterBlockHeader.addClassName("filtershown");
        }
      }
    }
  }
  // }}} addEncodedOpenedFilterTypes(sEncodedFilterTypes)
  
  
  
  
};


