/**
 * SimpleList allows you to construct a grouped list of DOM elements.
 * It provides some simple yet very convenient methods for working with a list of elements.
 *
 * Also includes powerful filtering capabilities for the list.
 * 
 * Note: the element attribute of "json" has special meaning when working with a SimpleList.
 * If present this attribute is expected to contain a JSON string that represents some object.
 * This attribute is converted to an associative array and is assigned to the element as property,
 * also named "json".  The JSON object takes precendence over element attributes when working with
 * the methods in SimpleList.
 * 
 * Note: the list name is included in events dispatched from the list.  
 * For example:
 * If you named the list "MyList", the following event would be dispatched when clearing filters:
 * MyListAllFiltersCleared
 * 
 */
var SimpleList = Class.create({
    
  /**
   * Constructor...
   * Note: the "name" must be unique because it is included in triggered event names. 
   * 
   * @param {String} name A unique name for the list.  
   * @param {Array} elements 
   */
  initialize: function(name, elements) {
    this.name = name;
    this.elements = [];
    var len = elements.length;
    
    for (var i = 0; i < len; i++) {
      var el = elements[i];
      this.addItem(el);
    }
  },
  
  /**
   * Adds an element to the list.
   * If the element contains a "json" attribute this attribute is converted to a JSON
   * object which takes precedence over attributes in list operations.
   * 
   * @param {Element} item The element to add to the list.
   */
  addItem: function(item) {
    item = $(item);
    item[this.name + "Index"] = this.elements.push(item) - 1;
    item[this.name] = this;

    // convert JSON attributes to JavaScript objects and append to element
    var jsonString = item.readAttribute("json"); 
    if (typeof(jsonString) == "string") {
      item.json = jsonString.evalJSON();
      item.json.elementId = item.id;
      item.json.element = item;
    }

    // extend element with custom methods
    var keys = Object.keys(this.listItemMethods);
    var len = keys.length;
    for (var i = 0; i < len; i++) {
      var key = keys[i];
      item[key] = this.listItemMethods[key];
    }
  },
  
  /**
   * Removes an element from the list.
   * @param {Element} item The element to remove.
   */
  removeItem: function(item) {
    var index = item[this.name + "Index"];
    
    if (typeof(index) == "number") {
      item[this.name + "Index"] = null;
      item[this.name] = null;
      
      this.elements.splice(index, 1);
    }
  },
  
  /**
   * Each element in the list is extended with the following methods.
   */
  listItemMethods: {    
    /**
     * Indicates if the element has a matching attibute name/value pair.
     * Checks the JSON object before checking the element attribute.
     * 
     * @param {String} attrName The attribute name to find.
     * @param {Object} attrValue The attribute value to find.
     */
    hasAttributePair: function(attrName, attrValue) {
      if (attrValue === null || attrValue === undefined) return false;
      
      var attr = this.getAttributeValue(attrName);
      var actualType = typeof(attr);
      var compareType = typeof(attrValue);
      
      if (attr === null || attr === undefined) return false;
      if (actualType == "object") attr = attr.toJSON();
      if (compareType == "object") attrValue = attrValue.toJSON();
      if (String(attr) == String(attrValue)) return true;
      return false;
    },
    
    /**
     * Gets the value for an attribute from the element.
     * Checks the JSON objects before checking the element attribute.
     * 
     * @param {String} attrName The attribute name.
     */
    getAttributeValue: function(attrName) {
      var attr = undefined;
      if (this.json) attr = this.json[attrName];
      if (!attr) attr = this.readAttribute(attrName);    
      if (!attr) attr = this[attrName];
      return attr;
    }, 
    
    /**
     * Applies all assigned filters.
     * 
     * @param {SimpleList} list The list to apply filters for.
     */
    applyFilters: function(list) {
      var key = list.name + "Filters"
      this[key] = this[key] || [];
      
      var len = this[key].length;
      var display = "";
      
      for (var x = 0; x < len; x++) {
        var func = this[key][x].func;
        var args = this[key][x].args;
                
        if (!func.apply(this, args)) {
          display = "none";
        }
      }
      
      if (this.style.display != display) {
        this.style.display = display;
      }
    },
    
    /**
     * Returns the position of the filter.
     * 
     * @param {SimpleList} list The list that the filter applies to.
     * @param {Function} filterFunc The filter function to find.
     */
    indexOfFilter: function(list, filterFunc) {
      var key = list.name + "Filters"
      this[key] = this[key] || [];
      var len = this[key].length;
      
      for (var i = 0; i < len; i++) {
        if (this[key][i].func == filterFunc) {
          return i;
        }
      }
        
      return -1;  
    },
    
    /**
     * Assigns a filter to the element.
     * 
     * @param {SimpleList} list The list that the filter belongs to.
     * @param {Function} filterFunc The function to invoke when filtering.
     * @param {Array} args The arguments to pass to the filter function.
     */
    assignFilter: function(list, filterFunc, args) {
      var key = list.name + "Filters";
      this[key] = this[key] || [];
      var index = this.indexOfFilter(list, filterFunc);
      
      if (index == -1) {
        // add for first time
        this[key].push({func: filterFunc, args: args});
      } else {
        // update arguments
        this[key][index].args = args;
      }
    },
    
    /**
     * Removes a filter from the element.
     * 
     * @param {SimpleList} list The list the filter belongs to.
     * @param {Function} filterFunc The filter function to remove.
     */
    clearFilter: function(list, filterFunc) {
      var key = list.name + "Filters"
      this[key] = this[key] || [];
      var index = this.indexOfFilter(list, filterFunc);
      
      if (index >= 0) {
        this[key].splice(index, 1);
      }
      
      this.applyFilters(list);
    }, 
    
    /**
     * Clears all filters.
     * @param {SimpleList} list The list to clear filters for.
     */
    clearAllFilters: function(list) {
      var key = list.name + "Filters"
      this[key] = [];
      if (this.style.display != "") this.style.display = "";
    }
  },
  
  /**
   * Returns a list of filters that are currently applied to the list.
   */
  filters: function() {
    // filters are applied globally to each element, 
    // so we can base this on the first element.
    if (this.elements.length > 0) {
      var key = this.name + "Filters";
      return this.elements[0][key];
    }
    
    return null;
  },
  
  /**
   * Filters the list.  
   * Shows or hides elements based on the result from the filter function.
   * The filter function is executed in the context of each element and should return a boolean.
   * 
   * Note: accepts additional arguments which will be passed to the filter function when it executes.
   * For example, list.filter(myFunc, arg1, arg2, arg3...)
   * 
   * @param {Function} filterFunc The function to execute in the context of each element.
   */
  filter: function(filterFunc) {
    if (!filterFunc) return;
    
    var args = [];
    var len = arguments.length;
    for (var i = 1; i < len; i++) {
      args.push(arguments[i]);
    }
    
    len = this.elements.length;
    for (var i = 0; i < len; i++) {
      var el = this.elements[i];
      el.assignFilter(this, filterFunc, args);      
      el.applyFilters(this);
    }
  },
  
  /**
   * Removes a filter from the list.
   * @param {Function} filterFunc The filter function to remove.
   */
  clearFilter: function(filterFunc) {
    if (!filterFunc) return;
    
    var len = this.elements.length;    
    for (var i = 0; i < len; i++) {
      var el = this.elements[i];
      el.clearFilter(this, filterFunc);
    }
  },  
  
  /**
   * Removes all filters applied to the list.
   */
  clearAllFilters: function() {
    var len = this.elements.length;
    var key = this.name + "Filters";
    
    for (var i = 0; i < len; i++) {
      var el = this.elements[i];
      el.clearAllFilters(this);
    }
    
    EventManager.fireEvent(this.name + "AllFiltersCleared");
  },
  
  sort: function(compareFunc) {
    // TODO: implement
  },

  /**
   * Shows all elements in the list by setting their style.display to "".
   */
  showItems: function() {
    var len = this.elements.length;
    for (var i = 0; i < len; i++) {
      var el = this.elements[i];
      el.style.display = "";
      VisualEffects.highlight(el);
    }
  },
  
  /**
   * Hides all elements in the list by setting their style.display to "none".
   */
  hideItems: function() {
    var len = this.elements.length;
    for (var i = 0; i < len; i++) {
      var el = this.elements[i];
      el.style.display = "none";
    }
  },

  /**
   * Shows all elements with a matching attribute key/value pair.
   * @param {String} attrName The attribute name to find.
   * @param {Object} attrValue The attribute value to find.
   */
  showItemsWithAttribute: function(attrName, attrValue) {
    var list = this.getItemsWithAttribute(attrName, attrValue);
    var len = list.length;
    for (var i = 0; i < len; i++) {
      var el = list[i];
      el.style.display = "";
      VisualEffects.highlight(el);
    }
  },
  showItemsWithAttr: this.showItemsWithAttribute,
  showWithAttr: this.showItemsWithAttribute,

  /**
   * Hides all elements with a matching attribute key/value pair.
   * @param {String} attrName The attribute name to find.
   * @param {Object} attrValue The attribute value to find.
   */
  hideItemsWithAttribute: function(attrName, attrValue) {
    var list = this.getItemsWithAttribute(attrName, attrValue);
    var len = list.length;
    for (var i = 0; i < len; i++) {
      var el = list[i];
      el.style.display = "none";
    }
  },
  hideItemsWithAttr: this.hideItemsWithAttribute,
  hideWithAttr: this.hideItemsWithAttribute,
 
  /**
   * Gets all elements in the list with the matching key/value attribute pair.
   * 
   * @param {String} attrName The name of the attribute to find.
   * @param {Object} attrValue The value of the attribute to find.
   * @return An array containing all matching elements.
   */
  getItemsWithAttribute: function(attrName, attrValue) {
    var list = [];
    var len = this.elements.length;
    for (var i = 0; i < len; i++) {
      var el = this.elements[i];
      if (el.hasAttributePair(attrName, attrValue)) {
        list.push(el);
      }
    }
    return list;
  },
  getItemsWithAttr: this.getItemsWithAttribute,
  getWithAttr: this.getItemsWithAttribute,
  
  /**
   * Gets all elements by a finder function.
   * The finder function is executed in the context of the element and should return a boolean.
   * If this function returns true, the element is included in the result set.
   * 
   * Note: accepts additional arguments which will be passed to the finder function when it executes.
   * For example, list.getItemsByFunction(myFunc, arg1, arg2, arg3...)
   * 
   * @param {Function} findFunc The finder function to execute in the context of the element.
   */
  getItemsByFunction: function(findFunc) {
    var args = [];
    var len = arguments.length;
    for (var i = 1; i < len; i++) {
      args.push(arguments[i]);
    }
    
    var list = [];
    var len = this.elements.length;
    for (var i = 0; i < len; i++) {
      var el = this.elements[i];
      if (findFunc.apply(el, args)) {
        list.push(el);
      }
    }
    return list;
  },
  getItemsByFunc: this.getItemsByFunction,
  getByFunc: this.getItemsByFunction

});
