/*
optimizations:
- remove / hide elements when moving out of view
*/

var SmoothScrollList = function(options) {
  if (typeof options !== 'object' || ! options.el) console.error('please initialize the list with an existing dom element');
  
  this.listData;
  this.listElement = options.el;
  this.listItemHeight;
  this.offset = 0;
  this.limit = 10;
  this.renderedElements = [];
  
  this.addListener(this.listElement, 'scroll', this.handleScroll.bind(this));

  var dataUrl = 'https://rawgithub.com/hc2p/smoothbiglist/master/scripts/data.json';
  var that = this;
  this.xhrJSONGet(dataUrl, function(result) {
    that.listData = result;
    that.renderList(result);
  });
};

//http://jsperf.com/innerhtml-vs-dogfragment/10
SmoothScrollList.prototype.createDomNode = function(item) {
  /*
    <li data-id="1">
      <img src="img/default-image-1.png" alt="">
      <p>John Doe</p>
    </li>
  */
  var li = document.createElement('li');
  
  var img = document.createElement('img');
  img.src = item.img;
  li.appendChild(img);

  var name = document.createElement('p');
  var content = document.createTextNode(item.id + ' ' + item.name);
  name.appendChild(content);
  li.appendChild(name);

  return li;
};

SmoothScrollList.prototype.getDomNode = function(item) {
  if (this.renderedElements[item.id]) return this.renderedElements[item.id];
  var node = this.createDomNode(item);
  this.renderedElements[item.id] = node;
  return node;
};

SmoothScrollList.prototype.getListItemHeight = (function() {
  var listItemHeight;
  return function(listData) {
    if (listItemHeight) return listItemHeight;
    
    //render one first element
    var node = this.getDomNode(listData[0]);
    this.listElement.appendChild(node);
    listItemHeight = node.getBoundingClientRect().height;
    return listItemHeight;
  };
})();

SmoothScrollList.prototype.getListHeight = (function() {
  var listHeight;
  return function() {
    listHeight = this.listElement.getBoundingClientRect().height;
    return listHeight; 
  }
})();

SmoothScrollList.prototype.getViewportMax = function() {
  var heightOfView = this.getListHeight();
  var listItemHeight = this.getListItemHeight(this.listData);
  viewportMax = Math.round((this.listElement.scrollTop + heightOfView) / listItemHeight);
  return viewportMax + 5;
};

SmoothScrollList.prototype.renderList = function() {
  var fragment = document.createDocumentFragment();
  //rendered first bunch? add a fixed set, otherwise only a few necessary to fill the view
  var limit = this.offset > 0 ? this.offset + this.limit : this.getViewportMax();
  var listSize = this.listData.length;
  for (var i = this.offset; i < limit && i < listSize; i++) {
    fragment.appendChild(this.getDomNode(this.listData[i]));
  }
  this.offset += this.limit;
  this.listElement.appendChild(fragment);
}

SmoothScrollList.prototype.handleScroll = (function() {
  var timeout;
  return function() {
    if (!timeout) {
      timeout = setTimeout(function() {
        var thirdLastElement = this.renderedElements[this.renderedElements.length - 3];
        if (this.isElementInViewport(thirdLastElement)) {
          this.renderList();
        }
        time = new Date();
        clearTimeout(timeout);
        timeout = null;
      }.bind(this), 250);
    }
  }
})();


//http://stackoverflow.com/a/7557433/546030
SmoothScrollList.prototype.isElementInViewport = function (el) {
  var rect = el.getBoundingClientRect();

  return (
    rect.top >= 0 &&
    rect.left >= 0 &&
    rect.bottom <= (window.innerHeight || document. documentElement.clientHeight) && /*or $(window).height() */
    rect.right <= (window.innerWidth || document. documentElement.clientWidth) /*or $(window).width() */
  );
}

SmoothScrollList.prototype.addListener = function(el, event, callback) {
  // W3C model
  if (el.addEventListener) {
    el.addEventListener(event, callback, false);
    return true;
  } 
  // Microsoft model
  else if (el.attachEvent) {
    return el.attachEvent('on' + event, callback);
  }
};

SmoothScrollList.prototype.xhrJSONGet = function(url, success, error) {
  if (window.XMLHttpRequest) { // Mozilla, Safari, ...
      xhr = new XMLHttpRequest();
  } else if (window.ActiveXObject) { // IE 8 and older
      xhr = new ActiveXObject("Microsoft.XMLHTTP");
  }
  xhr.onreadystatechange = function() {
    if (this.readyState === 4) {
      if (this.status === 200) {
        success(JSON.parse(this.responseText));
      } else {
        error('There was a problem with the request.');
      }
    }
  };
  xhr.open('GET', url);
  xhr.send();
  return xhr;
};

if (!Function.prototype.bind) {
  Function.prototype.bind = function (oThis) {
    if (typeof this !== "function") {
      // closest thing possible to the ECMAScript 5 internal IsCallable function
      throw new TypeError("Function.prototype.bind - what is trying to be bound is not callable");
    }

    var aArgs = Array.prototype.slice.call(arguments, 1), 
        fToBind = this, 
        fNOP = function () {},
        fBound = function () {
          return fToBind.apply(this instanceof fNOP && oThis
                                 ? this
                                 : oThis,
                               aArgs.concat(Array.prototype.slice.call(arguments)));
        };

    fNOP.prototype = this.prototype;
    fBound.prototype = new fNOP();

    return fBound;
  };
}
@@ -1481,33 +1481,32 @@
 istItemHeight = 
-(
 function() %7B%0A  v
@@ -1506,114 +1506,8 @@
 ) %7B%0A
-  var listItemHeight;%0A  return function(listData) %7B%0A    if (listItemHeight) return listItemHeight;%0A    %0A  
   //
@@ -1531,18 +1531,16 @@
 element%0A
-  
   var no
@@ -1560,16 +1560,21 @@
 DomNode(
+this.
 listData
@@ -1579,18 +1579,16 @@
 ta%5B0%5D);%0A
-  
   this.l
@@ -1619,17 +1619,19 @@
 ode);%0A  
- 
+var
  listIte
@@ -1669,34 +1669,32 @@
 tRect().height;%0A
-  
   return listIte
@@ -1706,18 +1706,11 @@
 ht;%0A
-  
 %7D;%0A
-%7D)();
 %0A%0ASm
@@ -1741,22 +1741,22 @@
 .get
-ListHeight
+ViewportMax
  = 
-(
 func
@@ -1774,56 +1774,20 @@
 var 
-listHeight;%0A  return function() %7B%0A    listHeight
+heightOfView
  = t
@@ -1838,143 +1838,8 @@
 ht;%0A
-    return listHeight; %0A  %7D%0A%7D)();%0A%0ASmoothScrollList.prototype.getViewportMax = function() %7B%0A  var heightOfView = this.getListHeight();%0A
   va

stop memorizing results of onetime executed function