ScrollList = function() {
  this.rendered_ = false;
  this.id_ = this.getNextUniqueId_();
  this.requestOutstanding_ = false;
};

ScrollList.BASE_ID_ = 'ScrollList.';

ScrollList.nextId_ = 0;

/**
 * Gets the next unique ID for the component.
 * @return {String} The next unique ID for the component.
 */
ScrollList.getNextUniqueId = function() {
  return ScrollList.BASE_ID_ + ScrollList.nextId_++;
};

/**
 * Gets the next unique ID for the component. This method is used in the
 * constructor to generate the unique ID for the component.
 *
 * NOTE: This method is placed on the prototype so that classes that inherit
 * this class can override this method and have the ID automatically set by
 * calling the parent class's constructor.
 *
 * @return {String} The next unique ID for the component.
 */
ScrollList.prototype.getNextUniqueId_ = function() {
  return ScrollList.getNextUniqueId();
};

/**
 * Gets the unique ID for the instance of this component.
 * @return {String} Unique element id.
 */
ScrollList.prototype.getId = function() {
  return this.id_;
};

/**
 * Gets the unique ID for the instance of this component.
 * @return {String} Unique element id.
 */
ScrollList.prototype.getScrollDivId = function() {
  return this.id_ + '-scrolldiv';
};

ScrollList.prototype.isRendered = function() {
  return this.rendered_;
};

ScrollList.prototype.render = function(opt_parent) {
  if (this.isRendered()) {
    throw new Error('Compenent already rendered');
  }

  var html = [];
  html.push('<div id="', this.getId(), '" class="scrollbox">',
    '<div id="', this.getScrollDivId(), '"></div>',
    '</div>');  

  var parentElement = opt_parent || document.body;
  parentElement.innerHTML = html.join("");

  $(this.getId()).onscroll = ScrollList_createCallback(this);
  this.rendered_ = true;
};

function ScrollList_createCallback(agenda) {
  return function() {
    agenda.handleScroll();
  };
}

/**
 * A function to call when the user scrolls to the bottom of the list
 * and it is time to request more content.
 * The handler will receive one argument -- a callback function.
 * The handler should apply the function to either:
 * <li>An array of strings of HTML content to insert into the list
 * <li>null, indicating that there is no more content to load
 */
ScrollList.prototype.setContentRequestHandler = function(fn) {
  this.contentRequestHandler_ = fn;
  this.loadedAllContent_ = false;
};

ScrollList.prototype.requestMoreContent_ = function() {
  if (this.requestOutstanding_) return;
  if (!this.contentRequestHandler_ || this.loadedAllContent_) return;
  this.requestOutstanding_ = true;
  this.showLoading();
  var self = this;
  var callback = function(entries) {
    if (entries == null) {
      self.loadedAllContent_ = true;
    } else {
      self.addEntries(entries);
    }
    self.requestOutstanding_ = false;
    self.hideLoading();
  };
  this.contentRequestHandler_.call(null, callback);
};

ScrollList.prototype.showLoading = function() {};
ScrollList.prototype.hideLoading = function() {};

ScrollList.prototype.handleScroll = function(e) {
  var container = $(this.getId());
  var scrollDiv = $(this.getScrollDivId());
  var total = scrollDiv.clientHeight;
  var offset = container.clientHeight + container.scrollTop;
  if ((offset / total) > 0.90) {
    this.requestMoreContent_();
  }
};

/**
 * @param Array<String<HTML>>
 */
ScrollList.prototype.addEntries = function(entries) {
  if (!this.isRendered()) {
    throw new Error('Component not rendered yet');
  }
  var scrollDiv = $(this.getScrollDivId());
  for (var i = 0; i < entries.length; ++i) {
    var fragment = htmlToDocumentFragment(entries[i]);
    scrollDiv.appendChild(fragment);
  }
};

