(function() {
  
  var nextId = 0;
  
  /*
   * paged browser base class
   *
   * Looking for usage like:
   *   var pb1 = new PagedBrowser({perPage: 6, data: function() { return []; }})
   *   var pb2 = new PagedBrowser({data: []});
   *
   *   pb1.setContainer('one');
   *   pb2.setContainer($('ouch'));
   */
  this.PagedBrowser = Class.extend({
    defaultOptions: {
      data : [],
      perPage: 6
    },
    
    data: [],
    container: null,
    resultsPane: null,
    nav: null,
    uniqueID: null,
    columns: [],
    
    /**
     * Constructor
     * @param {Array/Function} data The data or a function that returns data to
     *        use to populate the results
     * @param {Integer} perpage The number of records to show per page.
     * @param {String/Object} target The id or object to place the PagedBrowswer in
     */
    init: function(options){
      
      this.setOptions(options);
      
      //get a unique id to allow multiple on the page (works because JS is not threaded ... yet.)
      this.uniqueID = nextId;
      nextId++;
    },
    
    /**
     * Builds the options for this object based on the default options and the
     * passed in options
     */
    setOptions: function(options) {
      this.options = {};
      
      //add the default options
      for( var i in this.defaultOptions ) {
        this.options[i] = this.defaultOptions[i];
      }
      
      //add the passed in options
      for( i in options ) {
        this.options[i] = options[i];
      }
    },
    
    /**
     * Set the element that will contain the PagedBrowser
     * @param {String/Object} The selector for the object or the object itself
     *        that will contain the PagedBrowser
     * @note Accepts either a DOMElement or a jQuery object as well as a string
     *       that is a jQuery selector.
     */
    setContainer: function(obj) {
      if( typeof obj == 'string' ) {
        this.container = $(obj);                
      } else if ( obj instanceof jQuery ) {
        this.container = obj;
      } else {
        this.container = $(obj);
      }
      this.container.addClass('PagedBrowser');
    },
    
    /**
     * Get the data from the options.data member and put in this.data.  If
     * options.data is a function then the function is called.
     * @returns {Array} this.data
     */
    getData: function() {
      if( typeof this.options.data == 'function' ) {
        this.data = this.options.data();
      } else {
        this.data = this.options.data;
      }
      return this.data;
    },
    
    /**
     * Parse the hash portion of the query into an object of name, value pairs
     */
    parseURL: function() {
      var parms = {};
      if( window.location.hash.length > 0 ) {
        var args = unescape(window.location.hash).substr(1).split("&");
        for( var i = 0; i < args.length; i++ ) {
          var nv = args[i].split('=');
          if( nv.length > 1 ) {
            parms[nv[0]] = nv[1];
          } else if( nv.length == 1 ) {
            parms[nv[0]] = null;
          }
        }
      }
      
      return parms;
    },
    
    /**
     * Gets the page we are looking at out of the url.  Allows for copying the
     * link and sending the results to someone else.
     */
    getUrlPage: function(){
      var page = 0;
      //position to page specified in url. needs to be smarter. later.
      //TODO: make this smarter (able to handle multiple PagedBrowsers on a page)
      
      var parms = this.parseURL();
      if( parms['pb'+ this.uniqueID] !== undefined && parms['pb'+ this.uniqueID] !== null ) {
        if( parms['pb'+ this.uniqueID]  == 'table' ) {
          return 'table';
        }
        page = parseInt(parms['pb'+ this.uniqueID], 10);
      }
      
      return page;  
    },
    
    /**
     * Sets the url hash for the page we are looking at
     * @param {Integer} p The page we are looking at.
     */
    setUrlPage: function(p){
      var parms = this.parseURL();
      parms['pb'+ this.uniqueID] = p;
      window.location.hash = $.param(parms);
    },
    
    /**
     * Render the control to the page.
     */
    build: function() {
      this.getData();
      
      //render the place for the results
      this.resultsPane = $('<div class="hits"></div>');
      this.container.append(this.resultsPane);
      
      //populate the results pane
      var curPage = this.getUrlPage();
      if( curPage == 'table' ) {
        this.getTable();
      } else {
        this.getPage(curPage);
      }
      
      
      //render the paging for the paging
      this.renderPageNav();
      this.updateNav();
    },
    
    /**
     * Renders the records as a table and hides the paging links (next/previous).
     */
    getTable: function() {
      //get the height of the current results pane
      var height = this.resultsPane.height();
      if( height === 0 ) {undefined
        height = 250;
      }
      
      //clear out the old results
      this.resultsPane.empty();
      
      //indicate if no records
      if( this.data.length == 0 ) {
        this.resultsPane.append(this.renderNoResults());
        return;
      }
      
      var table = $('<table class="table_results" width="90%" cellpadding="0" cellspacing="0"></table>');
     
      //render the headers
      var row = $('<tr></tr>');
      for( var i = 0; i < this.columns.length; i++ ) {
        var cell = $('<th></th>');
        cell.html(this.columns[i].header);
        row.append(cell);
      }
      table.append(row);
      
      //render the data
      for( i = 0; i < this.data.length; i++ ) {
        table.append(this.renderTableRow(this.data[i]));
      }
      
      var container = $('<div></div>').css('height', height +'px').css('overflow', 'auto');
      container.append(table);
      
      this.resultsPane.append(container);
      
      //set the page to table view
      this.setUrlPage('table');
    },
    
    /**
     * Renders a row of data per the column spec
     * @param {Object} data One row of data.
     */
    renderTableRow: function(data) {
      var row = $('<tr></tr>');
      for( var j = 0; j < this.columns.length; j++ ) {
        cell = $('<td></td>');
        cell.html(data[this.columns[j].field]);
        row.append(cell);
      }
      return row;
    },
    
    /**
     * Render a specific page.
     */
    getPage: function(p) {
      //check sanity
      if( this.resultsPane === null ) { return; }
      if( p < 0 ){ p=0; }
      
      //make sure we were not given a page larger than the pages we have
      if( p > Math.floor(this.data.length / this.options.perPage) ) {
        p = Math.floor(this.data.length / this.options.perPage);
      }
      
      //clear out the old results
      this.resultsPane.empty();
      
      //indicate if no records
      if( this.data.length == 0 ) {
        this.resultsPane.append(this.renderNoResults());
        return;
      }
      
      var start = p * this.options.perPage;
      var end = (start + this.options.perPage < this.data.length ) ? (start + this.options.perPage) : this.data.length;
      
      for( var i = start; i < end; i++ ) {
        var user = this.renderItem(i, this.data);
        this.resultsPane.append(user);
      }
      
      //put in a div to allow for clearing
      this.resultsPane.append('<div class="clear"></div>');
      
      this.setUrlPage(p);

    },
    
    /**
     * Renders a div containing a message telling the user there were no results.
     * @returns {DOMElement|jQuery} must be able to be appended using jQuery.append()
     */
    renderNoResults: function() {
      var noResults = $('<div class="no_results">No results to display.</div>');
      return noResults;
    },
    
    /**
     * Renders a single item from the data. (one person or one record)
     * @param {Integer} i The row being rendered in data.
     * @param {Array} data The data to get the row from
     */
    renderItem: function(i, data) {
      return $('<div></div>').html(data[i].toString());
    },
    
    /**
     * Render the navigation bar.
     */
    renderPageNav: function() {
      this.nav = $(['<div class="bar">',
                    '<div class="prev">Previous</div>',
                    '<div class="next">Next</div>',
                    '<div class="list">Results as Table</div>',
                   '</div>'].join(''));
      this.nav.find('.next').click((function(obj) { return function() { obj.goNext(); return false; }; })(this) );
      this.nav.find('.prev').click((function(obj) { return function() { obj.goPrev(); return false; }; })(this) );
      if( this.columns.length > 0 ) {
        this.nav.find('.list').css('cursor', 'pointer').click((function(obj) { return function() { obj.getTable(); obj.updateNav(); return false; }; })(this));
      } else {
        this.nav.find('.list').remove();
      }
      this.container.append(this.nav);
    },
    
    /**
     * Updates the navigation display by hiding and showing next and previous when
     * appropriate.
     */
    updateNav: function(){
      var page=this.getUrlPage();
      
      if( this.nav === null ) {
        return;
      }
      
      //handle no records
      if( this.data.length == 0 ) {
        this.nav.find('.next').hide();
        this.nav.find('.prev').hide();
        this.nav.find('.list').hide();
        return;
      }
      
      //handle table mode
      if( page == 'table' && this.columns.length > 0 ) {
        this.nav.find('.next').hide();
        this.nav.find('.prev').hide();
        this.nav.find('.list').html('Paged Results').unbind('click').click((function(obj) { return function() { obj.getPage(0); obj.updateNav(); return false; }; })(this));;
        return;
      } else {
        this.nav.find('.list').html('Results as Table').unbind('click').click((function(obj) { return function() { obj.getTable(); obj.updateNav(); return false; }; })(this));;
      }
      
      //normal mode
      // only show next if we are not on the last page.
      if( (page + 1) * this.options.perPage < this.data.length ) {
        this.nav.find('.next').show();        
      } else {
        this.nav.find('.next').hide();        
      }
      
      // only show previous if we are not on the first page
      if( page > 0 ){
        this.nav.find('.prev').show();
      } else {
        this.nav.find('.prev').hide();
      }  
    },
    
    /**
     * Move to the next page.
     */
    goNext: function(){
      this.getPage(this.getUrlPage()+1);
      this.updateNav();
    },
    
    /**
     * Move to the previous page.
     */
    goPrev: function(){
      this.getPage(this.getUrlPage()-1);
      this.updateNav();
    },
    
    /**
     * Go to a specific page
     * @param {Integer} page The page to move to.
     */
    goPage: function(page){
      this.getPage(page);
      this.updateNav();
    }
    
  });
  
  /**
   * Allows for a common interface for browsing gomp users.
   */
  this.PagedPeopleBrowser = PagedBrowser.extend({
    defaultOptions: {
      data : [],
      perPage: 6,
      noRecordsMessage: null
    },
    
    mug: "/profileimage.php?pid={ID}&w=50&h=70",
    columns: [
      {header: 'First Name', field: 'first_name'},
      {header: 'Last Name', field: 'last_name'},
      {header: 'Class Of', field: 'year_finish'}
    ], 
    
    getProfileLink: function (data){
        if (data.directory){
            return "/directory/view/"+data['profile_id'];
        }else{
            return "/profile/view/"+data['profile_id'];
        }
    },
    /**
     * Renders a single item from the data. (one person or one record)
     * @param {Integer} i The row being rendered in data.
     * @param {Array} data The data to get the row from
     */
    renderItem: function(i, data) {
      var item = $(['<div class="hit profile_thumb profile_popup">',
                  '<div class="img"><img /></div>',
                  '<div class="info"></div>',
                '</div>'].join(''));
      
      mymug=(data[i].directory)?(this.mug.replace('{ID}', "0&directory=1")):(this.mug.replace('{ID}', data[i].profile_id));
      item.find('.img img')
        .attr('src', mymug)
        .attr('alt', data[i].first_name +' '+ data[i].last_name)
        .css('cursor', 'pointer')
        .click(function(link) { return function() {
	  var l = window.location;
	  // Add protocol and host if a relative link
	  window.location = (!l.match("://") ? l.protocol + "//" + l.host + "/" : "") + link;
        }; }(this.getProfileLink(this.data[i])))
      .end()
      .find('.info')
        .html(this.renderName(i) +'<br/>' + (this.data[i].year_finish === null ? '' : 'Class of '+ this.data[i].year_finish));
      
      var extra_info = $('<input type="hidden" />').attr('name', 'user_id').val(data[i].user_id);
      item.append(extra_info);
      extra_info = $('<input type="hidden" />').attr('name', 'profile_id').val(data[i].profile_id);
      item.append(extra_info);
      
      return item;
    },
    
    renderName:function(i){
      n=this.data[i].first_name+" "+this.data[i].last_name;
      if (this.data[i].grad_last_name !== null && this.data[i].grad_last_name != "" && this.data[i].grad_last_name!=this.data[i].last_name){
        n=n+' (' + this.data[i].grad_last_name + ')';
      }
      return '<a href="'+this.getProfileLink(this.data[i])+'">'+n+'</a>';              
    },
    
    /**
     * Renders a div containing a message telling the user there were no results.
     * @returns {DOMElement|jQuery} must be able to be appended using jQuery.append()
     */
    renderNoResults: function() {
      if( this.options.noRecordsMessage === null ) {
        return this._super();
      }
      var noResults = $('<div class="no_results"></div>').html(this.options.noRecordsMessage);
      return noResults;
    },
    
    /**
     * Renders a row of data per the column spec
     * @param {Object} data One row of data.
     */
    renderTableRow: function(data) {
      var row = $('<tr class="profile_popup"></tr>');
      for( var j = 0; j < this.columns.length; j++ ) {
        cell = $('<td></td>');
        cell.html(data[this.columns[j].field]);
        
        if( j == 0 ) {
          var extra_info = $('<input type="hidden"/>').attr('name', 'user_id').val(data.user_id);
          cell.append(extra_info);
          extra_info = $('<input type="hidden"/>').attr('name', 'profile_id').val(data.profile_id);
          cell.append(extra_info);
        }
        
        row.append(cell);
      }
      return row;
    }
    
  });
  
  /**
   *
   */
  this.MessageBrowser = PagedBrowser.extend({
    defaultOptions: {
      data : [],
      perPage: 6,
      selectThread: function(id) { }
    },
    
    mug: "/profileimage.php?pid={ID}&w=50&h=70",
    
    renderItem: function(i, data) {
      var item = $('<div class="conversation"></div>');
      
      item.append(this.renderPerson(i, data));
      item.append(this.renderMessage(i, data));
      item.append('<div class="clear"></div>');
      item.css('cursor', 'pointer');
      item.click(function(obj, id) { return function() { 
                       msg=obj.container.find(".fresh"+id);
                       msg.removeClass("fresh");
                       msg.removeClass("fresh"+id)
                       obj.options.selectThread(id);
                       return false; 
                    }; 
                 } (this, data[i].threadID));
      return item;
    },
    
    /**
     * Renders the person and their info
     */
    renderPerson: function(i, data) {
      var person = $(['<div class="person">', 
                      '<div class="img"><img/></div>',
                      '<div class="name"></div>',
                      '<div class="received"></div>',
                      '<div class="clear"></div>',
                     '</div>'].join(''));
      
      var lastUpdate = ConversationDate(data[i].dateReceived);
      
      person.find('img')
        .attr('src', this.mug.replace('{ID}', data[i].senderProfileID))
        .attr('alt', data[i].senderName)
      .end()
      .find('.name')
        .html(data[i].senderName)
      .end()
      .find('.received')
        .html(lastUpdate);
        
      return person;
    },
    
    /**
     * Renders the message subject and the last message in the conversation
     */
    renderMessage: function(i, data) {
      var msg = $(['<div class="msg">',
                    '<a class="subject"></a>',
                    '<div class="preview"></div>',
                   '</div>'].join(''));
      
      msg.find('.subject')
        .html(data[i].title)
        .css('cursor', 'pointer')
        //.click(function(obj, id) { return function() { obj.options.selectThread(id); return false; }; } (this, data[i].threadID))
      .end()
      .find('.preview')
        .html(HTMLEncode(data[i].preview))
      .end();
      if (data[i].fresh){
          msg.addClass("fresh");
          msg.addClass("fresh"+data[i].threadID); //so we can find it and clear it out
          msg.attr("title","Unread Message")
      }
      return msg;
    },
    
    /**
     * Renders a div containing a message telling the user there were no results.
     * @returns {DOMElement|jQuery} must be able to be appended using jQuery.append()
     */
    renderNoResults: function() {
      var noResults = $('<div class="no_results">You are not currently involved in any conversations.</div>');
      return noResults;
    }  
  });

  /**
  *
  */
 this.ImageBrowser = PagedBrowser.extend({
   defaultOptions: {
     data : [],
     perPage: 6,
     target: {}, // replace this in your init
     selectPhoto: function (i) {
       this.target.find("img").attr("src",this.data[i].Zoom);
       this.target.find("p").html(this.data[i].Caption);
     },
     close: null
   },
   
   /**
    * Render the navigation bar.
    */
   renderPageNav: function() {
     this.nav = $(['<div class="bar">',
                   '<div class="prev">Previous</div>',
                   '<div class="next">Next</div>',
                   '<div class="close">Close</div>',
                  '</div>'].join(''));
     this.nav.find('.next').click((function(obj) { return function() { obj.goNext(); return false; }; })(this) );
     this.nav.find('.prev').click((function(obj) { return function() { obj.goPrev(); return false; }; })(this) );
     if (this.options.close != null){
         this.nav.find('.close').click(this.options.close);
     }else{
         this.nav.find('.close').css('display','none')
     }
     
     this.container.append(this.nav);
   },
   select: function(selection){
     var data=this.data;
     var page=0;
     for (var i=0;i<data.length;i++){
         if (data[i].Id==selection){
             page=parseInt(i/this.options.perPage);
             break;
         }
     }
     this.getPage(page);
     this.updateNav();
     $(this.container.find(".id"+selection)).click(); 
   },
   renderItem: function(i, data) {
     var image = $(['<div class="gallery">', 
                      '<img class="image "/>',
                     '</div>'].join(''));
      
     image.find("img")
        .attr('src', data[i].Thumb)
        .attr('alt', data[i].Caption)
        .addClass('id'+data[i].Id)
        .click(function(obj, id) { return function() { obj.options.selectPhoto(id); return false; }; } (this, i));
     
     image.find("img").css('cursor', 'pointer');
     return image;
   }   
 });  
})();
