/**
 * Smart Lists
 * ~~~~~~~~~~~
 *
 * A Prototype/Scriptaculous extension that converts flat HTML lists of information into categorized,
 * paginated lists. This library is also available as a jQuery extension.
 *
 * @author  Ben Keen, http://www.benjaminkeen.com/software/smartlists/
 * @version 1.0.1
 * @date    March 21th 2008
 *
 *
 * Changelog
 * ~~~~~~~~~
 *
 * 1.0.2 - Apr 17 2008: bug fix for flags with 2 or more words
 * 1.0.1 - Mar 21 2008: added itemChangeDuration option
 * 1.0.0 - Mar 8 2008: initial release
 */

var SmartList = Class.create();

SmartList.prototype = {
  currentPage:     1,
  currentFlag:     "smartlist-all",
  options:         {},   // stores all options defined for this Smart List
  flagInfo:        null, // stores all unique flags for this Smart List
  listItems:       [],   // stores the Smart List item nodes
  itemFlagIndexes: [],   // stores the flag indexes for each particular Smart List item

  initialize: function() {
    var options = Object.extend({
      baseName:               "sl",
      itemClass:              "item",
      showFlagCount:          true,
      itemFlagClass:          "flags",
      itemFlagSeparator:      ", ",
      itemChangeEffect:       "Blind", // "FadeAppear", "Blind", "ShrinkGrow", ""
      itemChangeDuration:     1,
      pageChangeEffect:       "FadeAppear", // "FadeAppear", "Blind", "ShrinkGrow", ""
      pageChangeDuration:     0.5,
      numItemsPerPage:        3,
      paginationLeft:         "\u00ab",
      paginationRight:        "\u00bb",
      maxPaginationLinks:     10,
      defaultDropdownOptText: "All items",
      optgroups:              {}
    }, arguments[0] || {});

    // get all smart list item nodes
    var listItems = $$("#" + options.baseName + " ." + options.itemClass);
    var itemFlagIndexes = [];  // stores the flag indexes for each item
    var flagInfo = new Hash(); // flag string => [index, count]

    var currFlagIndex = 0;
    for (var i=0; i<listItems.length; i++)
    {
      // only display the first page; hide the rest
      listItems[i].style.display = (i < options.numItemsPerPage) ? "block" : "none";

      // each Smart List item can contain multiple flag sections
      var itemFlagNodes = $(listItems[i]).select("." + options.itemFlagClass);
      var currItemFlagIndexes = [];
      var linkNodes = [];

      for (var j=0; j<itemFlagNodes.length; j++)
      {
        var currItemFlagStrings = $(itemFlagNodes[j]).innerHTML.split(/\s+/);
        var flagSpan = document.createElement("span");

        // convert each flag to a Smart List link and while we're at it, keep track of all unique flags
        for (var k=0; k<currItemFlagStrings.length; k++)
        {
          // replace any non-breaking spaces with spaces
          var currFlag = currItemFlagStrings[k].replace(/&nbsp;/g, " ");

          flagIndex = currFlagIndex;
          if (flagInfo.get(currFlag) == undefined)
          {
            flagInfo.set(currFlag, [currFlagIndex, 1]);
            currFlagIndex++;
          }

          // this flag has already been added. Update the flag count and retrieve the flag index
          else
          {
            var oldFlagInfo = flagInfo.get(currFlag);
            flagIndex = oldFlagInfo[0];
            flagCount = oldFlagInfo[1] + 1;
            flagInfo.set(currFlag, [flagIndex, flagCount]);
          }

          var a = $(document.createElement("a"));
          a.setAttribute("href", "#");
          a.addClassName(options.baseName + "-flag" + flagIndex);
          a.appendChild(document.createTextNode(currFlag));
          flagSpan.appendChild(a);
          flagSpan.appendChild(document.createTextNode(options.itemFlagSeparator));
          currItemFlagIndexes.push(flagIndex);
        }

        // now remove the content of this flag node and insert the flag links
        $(itemFlagNodes[j]).innerHTML = "";
        $(itemFlagNodes[j]).appendChild(flagSpan);
      }

      // remove the last comma
      /*if (flagSpan.childNodes.length)
        flagSpan.removeChild(flagSpan.childNodes[flagSpan.childNodes.length-1]);*/ //commented out by sean so we don't have to mess with tags

      // keep track of all flag indexes for this item
      itemFlagIndexes[i] = currItemFlagIndexes;
    }

    // sort the Hash
    var sorted = flagInfo.keys().sort();
    var sortedFlagInfo = new Hash();

    for (var i=0; i<sorted.length; i++)
    {
      var value = flagInfo.get(sorted[i]);
      sortedFlagInfo.set(sorted[i], value);
    }

    // store the various settings of this Smart List for later use
    this.options = options;
    this.flagInfo = sortedFlagInfo;
    this.listItems = listItems;
    this.itemFlagIndexes = itemFlagIndexes;

    // prep the other aspects of the Smart List
    this.createPagination("smartlist-all");
    //this.addDropdown(); //commented out by sean so we don't have to mess with tags

    // show the entire Smart List (in case it was hidden)
    $(options.baseName).show();
  },


  /**
   * Called on initialization and whenever the user selects a flag. It re-creates the navigation
   * with the appropriate pagination links.
   */
  createPagination: function(flagIndex)
  {
    var listItems = this.listItems;
    var options   = this.options;
    var flagInfo  = this.flagInfo;

    var numPages = 0;
    if (flagIndex == "smartlist-all")
      numPages = Math.ceil(listItems.length / options.numItemsPerPage);
    else
    {
      var flagCount = this._getFlagCountFromFlagIndex(flagIndex);
      numPages = Math.ceil(flagCount / options.numItemsPerPage);
    }

    $(options.baseName + "-pagination").innerHTML = "";
    if (numPages <= 1)
      return;

    var pagination = document.createElement("span");

    var previousSpan = document.createElement("span");
    previousSpan.setAttribute("id", options.baseName + "-pagination-previous");
    previousSpan.appendChild(document.createTextNode(this.options.paginationLeft));
    pagination.appendChild(previousSpan);

    var halfTotalNavPages = Math.floor(options.maxPaginationLinks / 2);

    for (var i=1; i<=numPages; i++)
    {
      var span = document.createElement("span");
      span.setAttribute("id", options.baseName + "-page" + i);

      if (i == 1)
      {
        span.setAttribute("class", options.baseName + "-pagination-selected");
        span.setAttribute("className", options.baseName + "-pagination-selected");
      }

      if (i > halfTotalNavPages)
        span.style.cssText = "display:none";

      var a = document.createElement("a");
      a.setAttribute("href", "#");
      a.onclick = this.changePage.bindAsEventListener(this, i);
      a.appendChild(document.createTextNode(i));
      span.appendChild(a);

      pagination.appendChild(span);
    }

    var nextSpan = document.createElement("span");
    nextSpan.setAttribute("id", options.baseName + "-pagination-next");
    nextSpan.appendChild(this._getPaginationNextLinkNode());
    pagination.appendChild(nextSpan);

    $(options.baseName + "-pagination").appendChild(pagination);
  },


  /**
   * Called on Smart List initialization; creates the sorted dropdown contents.
   */
  /*addDropdown: function() //commented out by sean so we don't have to mess with tags
  {
    var options  = this.options;
    var flagInfo = this.flagInfo;

    var s = document.createElement("select");
    s.onchange = this.filterByFlag.bindAsEventListener(this);

    var defaultOpt = document.createElement("option");
    defaultOpt.setAttribute("value", "smartlist-all");
    defaultOpt.appendChild(document.createTextNode(options.defaultDropdownOptText));
    s.appendChild(defaultOpt);

    var currSmartList = this;

    flagInfo.each(function(pair) {
      var flag  = pair.key;
      var index = pair.value[0];
      var count = pair.value[1];

      var opt = document.createElement("option");
      opt.setAttribute("value", index);
      var displayText = (options.showFlagCount) ? flag + " (" + count + ")" : flag;
      opt.appendChild(document.createTextNode(displayText));
      s.appendChild(opt);

      $$("." + options.baseName + "-flag" + index).invoke('observe', 'click', currSmartList.filterByFlag.bindAsEventListener(currSmartList, index));
    });


    $(options.baseName + "-flag-dropdown").appendChild(s);
  },*/


  /**
   * Called whenever the user selects a flag by clicking on its link, or selecting it from the dropdown
   * list. This function filters all the Smart List items by the selected flag, re-selects the appropriate
   * value in the dropdown list and updates the pagination. The flag index is passed in one of two ways: through
   * the second parameter, or by tracking the event's source element and grabbing its value (with the dropdown).
   */
  filterByFlag: function(event, index) {

    var flagIndex = index;
    if (Event.element(event).value != undefined)
      flagIndex = Event.element(event).value;

    var listItems = this.listItems;
    var options   = this.options;
    var itemFlagIndexes = this.itemFlagIndexes;

    // loop through the list items and display the first page
    var count = 0;
    for (var i=0; i<listItems.length; i++)
    {
      // once the first page is full, hide all remaining items
      if (count >= options.numItemsPerPage)
      {
        listItems[i].hide();
        continue;
      }

      if (itemFlagIndexes[i].include(flagIndex) || flagIndex == "smartlist-all")
      {
        if (listItems[i].style.display == "none")
        {
          switch (options.itemChangeEffect)
          {
            case "Blind":
              Effect.BlindDown(listItems[i], { duration: options.itemChangeDuration } );
              break;
            case "FadeAppear":
              Effect.Appear(listItems[i], { duration: options.itemChangeDuration });
              break;
            case "ShrinkGrow":
              Effect.Grow(listItems[i], { duration: options.itemChangeDuration });
              break;
            default:
              listItems[i].show();
              break;
          }
        }
        count++;
      }
      else
      {
        if (listItems[i].style.display != "none")
        {
          switch (options.itemChangeEffect)
          {
            case "Blind":
              Effect.BlindUp(listItems[i], { duration: options.itemChangeDuration });
              break;
            case "FadeAppear":
              Effect.Fade(listItems[i], { duration: options.itemChangeDuration });
              break;
            case "ShrinkGrow":
              Effect.Shrink(listItems[i], { duration: options.itemChangeDuration });
              break;
            default:
              listItems[i].hide();
              break;
          }
        }
        else
          listItems[i].hide();
      }
    }

    this._selectDropdownOption(flagIndex);
    this.createPagination(flagIndex);
    this.currentPage = 1;
    this.currentFlag = flagIndex;

    Event.stop(event);
  },


  /**
   * Called whenever the user clicks on a new nav page.
   */
  changePage: function(event, page) {
    var options     = this.options;
    var currentPage = this.currentPage;

    if (page == currentPage)
      return;

    if (page == "next")
      page = currentPage + 1;
    else if (page == "previous")
      page = currentPage - 1;

    $(options.baseName + "-page" + currentPage).removeClassName(options.baseName + "-pagination-selected");
    $(options.baseName + "-page" + page).addClassName(options.baseName + "-pagination-selected");

    // hide/fade out the old page and show/fade in the new!
    var selectedFlagItems = (this.currentFlag == "smartlist-all") ? this.listItems : this._getItemsByFlag(this.currentFlag);

    var firstItemToHide = (currentPage - 1) * options.numItemsPerPage;
    var maxLastItem     = firstItemToHide + options.numItemsPerPage;
    var lastItemToHide  = (maxLastItem > selectedFlagItems.length) ? selectedFlagItems.length : maxLastItem;

    var firstItemToShow = (page - 1) * options.numItemsPerPage;
    var maxLastItem     = firstItemToShow + options.numItemsPerPage;
    var lastItemToShow  = (maxLastItem > selectedFlagItems.length) ? selectedFlagItems.length : maxLastItem;

    for (var i=0; i<selectedFlagItems.length; i++)
    {
      if (i >= firstItemToHide && i < lastItemToHide)
      {
        switch (options.pageChangeEffect)
        {
          case "Blind":
            Effect.BlindUp(selectedFlagItems[i], { duration: options.pageChangeDuration });
            break;
          case "FadeAppear":
            Effect.Fade(selectedFlagItems[i], { duration: options.pageChangeDuration });
            break;
          case "ShrinkGrow":
            Effect.Shrink(selectedFlagItems[i], { duration: options.pageChangeDuration });
            break;
          default:
            $(selectedFlagItems[i]).hide();
            break;
        }
      }
      if (i >= firstItemToShow && i < lastItemToShow)
      {
        switch (options.pageChangeEffect)
        {
          case "Blind":
            Effect.BlindDown(selectedFlagItems[i], { delay: options.pageChangeDuration, duration: options.pageChangeDuration });
            break;
          case "FadeAppear":
            Effect.Appear(selectedFlagItems[i], { delay: options.pageChangeDuration, duration: options.pageChangeDuration });
            break;
          case "ShrinkGrow":
            Effect.Grow(selectedFlagItems[i], { delay: options.pageChangeDuration, duration: options.pageChangeDuration });
            break;
          default:
            $(selectedFlagItems[i]).show();
            break;
        }
      }
    }

    // lastly, update the pagination links
    var lastPage = Math.ceil(selectedFlagItems.length / options.numItemsPerPage);
    if (page == 1)
      $(options.baseName + "-pagination-previous").innerHTML = options.paginationLeft;
    else
    {
      $(options.baseName + "-pagination-previous").innerHTML = "";
      $(options.baseName + "-pagination-previous").appendChild(this._getPaginationPreviousLinkNode());
    }

    if (page == lastPage)
      $(options.baseName + "-pagination-next").innerHTML = options.paginationRight;
    else
    {
      $(options.baseName + "-pagination-next").innerHTML = "";
      $(options.baseName + "-pagination-next").appendChild(this._getPaginationNextLinkNode());
    }

    // only show the appropriate navigation links (max: options.maxPaginationLinks)
    var totalVisible = 0;
    var halfTotalNavPages = Math.floor(options.maxPaginationLinks / 2);
    var firstVisiblePage  = (page > halfTotalNavPages) ? page - halfTotalNavPages : 1;
    var lastVisiblePage   = ((page + halfTotalNavPages) < lastPage) ? page + halfTotalNavPages : lastPage;

    for (var i=1; i<=lastPage; i++)
    {
      if (i < firstVisiblePage)
        $(options.baseName + "-page" + i).hide();
      if (i > lastVisiblePage)
        $(options.baseName + "-page" + i).hide();

      if (i >= firstVisiblePage && i <= lastVisiblePage)
        $(options.baseName + "-page" + i).show();
    }

    this.currentPage = page;

    Event.stop(event);
  },


  _getFlagCountFromFlagIndex: function(flagIndex)
  {
    var flagCount = null;
    this.flagInfo.each(function(pair) {
      var key   = pair.key;
      var value = pair.value;

      if (flagIndex == value[0])
        flagCount = value[1];
    });

    return flagCount;
  },


  _getItemsByFlag: function(flagIndex)
  {
    var nodes = [];
    for (var i=0; i<this.listItems.length; i++)
    {
      if (this.itemFlagIndexes[i].include(flagIndex))
        nodes.push(this.listItems[i]);
    }

    return nodes;
  },

  _getPaginationNextLinkNode: function()
  {
    var nextLink = document.createElement("a");
    nextLink.setAttribute("href", "#");
    nextLink.onclick = this.changePage.bindAsEventListener(this, "next");
    nextLink.appendChild(document.createTextNode(this.options.paginationRight));

    return nextLink;
  },

  _getPaginationPreviousLinkNode: function()
  {
    var previousLink = document.createElement("a");
    previousLink.setAttribute("href", "#");
    previousLink.onclick = this.changePage.bindAsEventListener(this, "previous");
    var left = this.options.paginationLeft;
    previousLink.appendChild(document.createTextNode(left));

    return previousLink;
  },

  _selectDropdownOption: function(flagIndex)
  {
    var options = this.options;
    var dd_div = $(options.baseName + "-flag-dropdown").getElementsByTagName("select");
    var dd = dd_div[0];
    for (var i=0; i<dd.options.length; i++)
    {
      if (dd.options[i].value == flagIndex)
      {
        dd.options[i].selected = true;
        break;
      }
    }
  }
};