//
// Javascript Pagination Class 0.41
// 
// Version history:
// 
// 0.41
//   Kept page when leaving the current page by adding anchors to the
// page like #page2, #page3 etc
//   Made pagination scroll to the top of the page when a link is clicked
//   Refactored the way that links are made by adding a reusable function
// 
// 0.4
//   Added more options, including ability to show 'less'
// direct page links and first and last page links.
//   Improved the way the pages are generated now the 
// container element is searched for matching 'record'
// elements and the this.records is built auto-magically.
// 
// 0.3 
//   Fixed IE7 compatibility 
// 
// 0.2
//   Added functionality to rebuild the pagination contols on each page change
//   Other minor fixes
//   Fixed IE6 compatibility
//   Added variable to prevent animations running if effects are enabled
// 
// 0.1
//   Initial build
// 
// Requires prototype.js (and scrip.taculo.us for effects)
// 
var Pagination = Class.create();
Pagination.prototype = {

  // set up variables
  results: null,
  container: null,
  resultPages: [],
  controls: null,
  pagination: null,
  page: 0,
  pages: 0,
  links: null,
  prevLink: null,
  nextLink: null,
  records: 0,
  firstEllipsis: false,
  secondEllipsis: false,
  // 
  
  // initialize
  // 
  // parses options and sets up the pagination
  initialize: function(options) {
    this.options = Object.extend({
      // options:
      // 
      // firstText (string)
      // The text displayed on the 'First' link
      firstText: '\xAB\xAB First',
      //
      // lastText (string)
      //  "          "      "   "  'Last' link
      lastText: 'Last \xBB\xBB',
      //
      // prevText (string)
      //  "          "      "   "  'Previous' link
      prevText: '\xAB Previous',
      //
      // prevText (string)
      //  "          "      "   "  'Next' link
      nextText: 'Next \xBB',
      //
      // showFirstAndLast (boolean)
      // Whether or not to show the 'First' and 'Last' links
      showFirstAndLast: false,
      //
      // showNextAndPrevious (boolean)
      //    "           "        "  'Next' and 'Previous' link
      showNextAndPrevious: true,
      //
      // showPageLinks (boolean)
      //    "           "        "  numbered page links
      showPageLinks: true,
      //
      // showLeadingAndTrailing (integer)
      // specify the number of leading, trailing page numbers
      showLeadingAndTrailing: 0,
      //
      // showLessPageLinks (integer)
      // if showPageLinks is true then this option can display
      // fewer links in the list, eg, if this is set to 3 and
      // you have 15 pages on page 1 you would see:
      //   1 2 3 4 ... 14 15
      // on page 7 you would see:
      //   1 2 ... 4 5 6 7 8 9 10 ... 14 15
      // (Based on showLeadingAndTrailing set to 2)
      showLessPageLinks: 0,
      // 
      // showSummary (boolean)
      // if showPageLinks is false this can  display a summary
      // like 'Page 4 of 72' (showPageLinks _must_ be false!)
      showSummary: false,
      //
      // summaryTag (string)
      // The tag to create the summary in
      summaryTag: 'p',
      //
      // summaryText (string)
      // The summary text can accept variables of {page} and {pages}
      // which are the current page and total pages respectively
      summaryText: 'Page {page} of {pages}',
      //
      // summaryClassName (string)
      // The className to apply to the summary tag
      summaryClassName: '',
      //
      // perPage (integer)
      // the number of records to be displayed per page
      perPage: 10,
      //
      // control
      // a CSS query matching the elements you wish to have the
      // pagination inserted
      control: 'div.pageControl',
      // 
      // container
      // a CSS query to match the container of the records
      container: 'div#venueList',
      // 
      // recordsMatch
      // a CSS query to match each record element (will be used
      // in conjunction with the container option, eg, div#records p,
      // to only return the child elements of the container)
      recordsMatch: 'div.browseResult',
      // 
      // pageSeparator
      // an ellipsis used if showLessPageLinks is true
      // pageSeparator: '\x85', // 'true' ellipsis doesn't seem to work...
      pageSeparator: '...',
      //
      // paginationClassName
      // a className to be applied to the pagination controls
      paginationClassName: '',
      // 
      // hideIfOnePage
      // hide the pagination controls if there is only one page of results
      hideIfOnePage: true,
      //
      // effects
      // whether or not to use the effects library
      effects: true
    }, options || {});
    // 
    
    // set the window.allowPageChange variable to true
    window.allowPageChange = true;
    
    // set up the container as the first element matching
    // this.options.container
    this.container = $$(this.options.container)[0];
    
    // set this.controls to the elements matching this.options.control
    this.controls = $$(this.options.control);
    
    // set this.resultPages to the elements that match this.options.recordsMatch
    // that are children of this.options.container
    this.results = $$(this.options.container + ' ' + this.options.recordsMatch);
    
    // set the records to the number of elements in the container
    this.records = this.results.length;
    
    // update the pages as per the records as per the perPage option
    this.pages = Math.ceil(this.records / this.options.perPage);
    
    // loop through all the results and add them to new pagination divs
    for (var i = 0; i < this.pages; i++) {
      
      // create the resultPage div
      this.resultPages[i] = new Element('div', {
        'page': (i + 1)
      });
      
      // loop through the actual results
      for (var j = 0; j < this.options.perPage; j++) {
        // check that we haven't totally emptied the array
        if (this.results.length > 0) {
          // append the result to the new pagination div and remove it from the DOM elsewhere
          this.resultPages[i].insert(this.results.first().remove());
          // remove it from the collection of results
          this.results = this.results.without(this.results.first());
        // if we have, break out
        } else {
          break;
        }
      }
      
      this.container.insert(this.resultPages[i]);
    }
        
    // check if user's used the back button
    var link = window.location.href;
    
    if (link) {
      // break the link into before and after the #
      var url = link.split('#');
      
      // if we've got 2 elements, then look for a page
      if ((url) && (url.length == 2) && url[1].match(/^page\d+$/)) {
        // if we've got a page number...
        var number = parseInt(url[1].replace(/^page/, ''));
        // use it!
        this.choosePage((!(isNaN(number)) && (number <= this.pages) && (number > 0)) ? number : 1, false);
      } else {
        // show page 1
        this.choosePage(1, false);
      }
    } else {
      // show page 1
      this.choosePage(1, false);
    }
  },
  
  //
  // show
  // 
  // appends the controls to the main page
  show: function() {
    // if we don't need pagination, don't display it
    if (this.options.records <= this.options.perPage && this.options.hideIfOnePage) {
      return false;
    }

    // loop through all the controls and add the controlObj to each
    for (var i = 0; i < this.controls.length; i++) {

      // remove all HTML from the element
      this.controls[i].empty();

      // replace with the control
      this.controls[i].update(this.create());
    }
    // 
    
    return true;
  },
  
  //
  // create
  // 
  // build the pagination controls
  create: function() {
    // destroy the element
    this.pagination = null;
    this.firstEllipsis = false;
    this.secondEllipsis = false;
    
    // create the base pagination element
    this.pagination = new Element('p');

    this.pagination.addClassName(this.options.paginationClassName);
    
    // if we're adding a summary
    if (this.options.showSummary) {
      
      var recordsStart = ((this.options.perPage * this.page) - (this.options.perPage - 1));
      var recordsEnd = ((this.options.perPage * this.page) > this.records) ? this.records : this.options.perPage * this.page;
      
      var summary = new Element(this.options.summaryTag, {
        'className': this.options.summaryClassName
      });
      summary.appendChild(
        document.createTextNode(
          this.options.summaryText.replace(/\{page\}/g, this.page).replace(/\{pages\}/g, this.pages).replace(/\{recordsStart\}/, recordsStart).replace(/\{recordsEnd\}/, recordsEnd).replace(/\{recordsTotal\}/, this.records)
        )
      );
      
      this.pagination.appendChild(summary);
    }

    if (this.options.showFirstAndLast) {
      this.pagination.appendChild(this.buildLink(1, this.options.firstText));
    }
    
    if (this.options.showNextAndPrevious) {
      this.pagination.appendChild(this.buildLink(this.page - 1, this.options.prevText));
    }
    
    // setup the main page number links
    if (this.options.showPageLinks) {
      this.links = [];

      // show all the links
      for (var i = 0; i < this.pages; i++) {
        
        var page = (i + 1);
        
        var showLessPageLinksConditions = (page <= this.options.showLeadingAndTrailing || page > (this.pages - this.options.showLeadingAndTrailing) || (page >= (this.page - parseInt(this.options.showLessPageLinks)) && page <= (this.page + parseInt(this.options.showLessPageLinks))));
        
        if ((this.options.showLessPageLinks && showLessPageLinksConditions) || !(this.options.showLessPageLinks)) {
          var link = this.buildLink(page);
        } else {
          if (this.options.showLeadingAndTrailing && (this.options.pageSeparator) && ((page < this.page && !(this.firstEllipsis)) || (page > this.page && !(this.secondEllipsis)))) {
            if (page < this.page) {
              this.firstEllipsis = true;
            } else if (page > this.page) {
              this.secondEllipsis = true;
            }
            var link = this.buildLink(0, this.options.pageSeparator);
          }
        }

        this.pagination.appendChild(link);
        this.links[this.links.length] = link;
      }
    }
    // 
    
    if (this.options.showNextAndPrevious) {
      this.pagination.appendChild(this.buildLink(this.page + 1, this.options.nextText));
    }

    if (this.options.showFirstAndLast) {
      this.pagination.appendChild(this.buildLink(this.pages, this.options.lastText));
    }
    
    return this.pagination;
  },
  
  buildLink: function(page, text) {
    if (!(text)) {
      text = page;
    }
    
    if (page == this.page || page < 1 || page > this.pages) {
      var link = new Element('span');
      link.appendChild(document.createTextNode(text))
    } else {
      var link = new Element('a', {
        'href': '#page' + page
      });
      if (this.options.effects) {
        link.onclick = function() {
          if (this.controls[0].viewportOffset().top < 0) {
            new Effect.ScrollTo(this.controls[0], { duration: 0.4 });
          }
        }.bindAsEventListener(this);
      } else {
        link.onclick = function() {
          if (this.controls[0].viewportOffset().top < 0) {
            this.controls[0].scrollTo();
          }
        }.bindAsEventListener(this);
      }
      link.appendChild(document.createTextNode(text))
      Event.observe(link, 'click', this.choosePageHandler.bindAsEventListener(this, page));
    }
    
    return link;
  },
  
  //
  // choosePageHandler
  // 
  // handler for the bindAsEventListener function
  choosePageHandler: function(e) {
    // get the arguments
    var data = $A(arguments);
    
    // remove the first argument (the object)
    data.shift();

    // work out what to do
    return this.choosePage(data[0]);
  },
  // 
  
  //
  // choosePage
  // 
  // shows the requested page
  choosePage: function(page, effects) {
    effects = (effects == false) ? false : this.options.effects;
    
    // if the page is out of bounds, return
    if (page > this.pages || page == this.page || !(window.allowPageChange)) {
      return false;
    }
    
    // set this.page to the requested page
    this.page = page;
    
    // if we're using effects and the container is set...
    if (effects) {
      window.allowPageChange = false;
      Effect.BlindUp(this.container, { duration: 0.8, queue: { position: 'front', scope: 'pagination' } } );
    }
    
    // loop through the results pages looking for the current page
    for (var i = 0; i < this.resultPages.length; i++) {
      // if we get a match
      if (this.resultPages[i].getAttributeNode('page').value == page) {
        // if we're using effects, time out the request
        if (effects) {
          Effect.Appear(this.resultPages[i], { duration: 0.0, queue: { position: 'end', scope: 'pagination' } } );
        } else {
          // otherwise do it right away
          this.resultPages[i].show();
        }
      } else {
        if (effects) {
          Effect.Fade(this.resultPages[i], { duration: 0.0, queue: { position: 'end', scope: 'pagination' } } );
        } else {
          this.resultPages[i].hide();
        }
      }
    }
    // 
    
    // ... using effects, then animate it back out
    if (effects) {
      Effect.BlindDown(this.container, { duration: 0.8, queue: { position: 'end', scope: 'pagination' } } );
      window.setTimeout(function() { window.allowPageChange = true; }, 1800);
    }
    
    this.show();
    
    return true;
  }
}
