/* Clusterize.js - v0.18.1 - 2018-01-02
 http://NeXTs.github.com/Clusterize.js/
 Copyright (c) 2015 Denis Lukov; Licensed GPLv3 */

 ;(function(name, definition) {
  if (typeof module != 'undefined') module.exports = definition();
  else if (typeof define == 'function' && typeof define.amd == 'object') define(definition);
  else this[name] = definition();
}('Clusterize', function() {
"use strict"

// detect ie9 and lower
// https://gist.github.com/padolsey/527683#comment-786682
let ie = (function(){
  let v = 3;
  for( 
      let el = document.createElement('b'),
      all = el.all || [];
      el.innerHTML = '<!--[if gt IE ' + (++v) + ']><i><![endif]-->',
      all[0];
  ){}
  return v > 4 ? v : document.documentMode;
}()),
is_mac = navigator.platform.toLowerCase().indexOf('mac') + 1;
let Clusterize = function(data) {
  if( ! (this instanceof Clusterize))
    return new Clusterize(data);
  let self = this;

  let defaults = {
    rows_in_block: 50,
    blocks_in_cluster: 4,
    tag: null,
    offset:0,
    show_no_data_row: true,
    no_data_class: 'clusterize-no-data',
    no_data_text: 'No data',
    keep_parity: true,
    callbacks: {}
  }
  // let topHeightOffset = 0;
  // public parameters
  self.options = {};
  let options = ['rows_in_block', 'blocks_in_cluster', 'offset','show_no_data_row', 'no_data_class', 'no_data_text', 'keep_parity', 'tag', 'callbacks'];
  for(let i = 0, option; option = options[i]; i++) {
    self.options[option] = typeof data[option] != 'undefined' && data[option] != null
      ? data[option]
      : defaults[option];
  }

  let elems = ['scroll', 'content'];
  for(let i = 0, elem; elem = elems[i]; i++) {
    self[elem + '_elem'] = data[elem + 'Id']
      ? document.getElementById(data[elem + 'Id'])
      : data[elem + 'Elem'];
    if( ! self[elem + '_elem'])
      throw new Error("Error! Could not find " + elem + " element");
  }

  // tabindex forces the browser to keep focus on the scrolling list, fixes #11
  if( ! self.content_elem.hasAttribute('tabindex'))
    self.content_elem.setAttribute('tabindex', 0);

  // private parameters
  let rows = isArray(data.rows)
      ? data.rows
      : self.fetchMarkup(),
    cache = {},
    scroll_top = self.scroll_elem.scrollTop;
    console.log("scroll_top"+ scroll_top);
  // append initial data
  console.log("initial data");
  self.insertToDOM(rows, cache);

  // restore the scroll position
  self.scroll_elem.scrollTop = scroll_top;

  // adding scroll handler
  let last_cluster = false,
  scroll_debounce = 0,
  pointer_events_set = false,
  scrollEv = function() {
    // fixes scrolling issue on Mac #3
    if (is_mac) {
        if( ! pointer_events_set) self.content_elem.style.pointerEvents = 'none';
        pointer_events_set = true;
        clearTimeout(scroll_debounce);
        scroll_debounce = setTimeout(function () {
            self.content_elem.style.pointerEvents = 'auto';
            pointer_events_set = false;
        }, 50);
    }

    console.log("scroll_top"+ scroll_top);
    // console.log("last_cluster"+last_cluster);
    // console.log("last_cluster 1 "+self.getClusterNum());
    if (last_cluster != (last_cluster = self.getClusterNum())){
      console.log("last_cluster");
      self.insertToDOM(rows, cache);
    }
    if (self.options.callbacks.scrollingProgress)
      self.options.callbacks.scrollingProgress(self.getScrollProgress());
  },
  resize_debounce = 0,
  resizeEv = function() {
    clearTimeout(resize_debounce);
    resize_debounce = setTimeout(self.refresh, 100);
  }
  on('scroll', self.scroll_elem, scrollEv);
  on('resize', window, resizeEv);

  // public methods
  self.destroy = function(clean) {
    off('scroll', self.scroll_elem, scrollEv);
    off('resize', window, resizeEv);
    self.html((clean ? self.generateEmptyRow() : rows).join(''));
  }
  self.refresh = function(force) {
    if(self.getRowsHeight(rows) || force) self.update(rows);
  }
  self.gotoTop = function() {
    //off('scroll', self.scroll_elem, scrollEv);
    self.scroll_elem.scrollTop = 0;
    resize_debounce = 0;
    
    on('scroll', self.scroll_elem, scrollEv);
    on('resize', window, resizeEv);

  }
  self.update = function(new_rows) {
    rows = isArray(new_rows)
      ? new_rows
      : [];
    let scroll_top = self.scroll_elem.scrollTop;
    // fixes #39
    if(rows.length * self.options.item_height < scroll_top) {
      console.log("rows.length"+rows.length);
      self.scroll_elem.scrollTop = 0;
      last_cluster = 0;
    }
    self.insertToDOM(rows, cache);
    self.scroll_elem.scrollTop = scroll_top;
  }
  self.clear = function() {
    self.update([]);
  }
  self.getRowsAmount = function() {
    return rows.length;
  }
  self.getScrollProgress = function() {
    return this.options.scroll_top / (rows.length * this.options.item_height) * 100 || 0;
  }
  self.setTopHeightOffset = function(offset){
    //console.log("offsetoffsetoffsetoffsetoffsetoffsetoffsetoffsetoffsetoffsetoffset"+offset);
    this.options.offset = offset;
  }

  let add = function(where, _new_rows) {
    let new_rows = isArray(_new_rows)
      ? _new_rows
      : [];
    if( ! new_rows.length) return;
    rows = where == 'append'
      ? rows.concat(new_rows)
      : new_rows.concat(rows);
    self.insertToDOM(rows, cache);
  }
  self.append = function(rows) {
    add('append', rows);
  }
  self.prepend = function(rows) {
    add('prepend', rows);
  }
}

Clusterize.prototype = {
  constructor: Clusterize,
  // fetch existing markup
  fetchMarkup: function() {
    let rows = [], rows_nodes = this.getChildNodes(this.content_elem);
    while (rows_nodes.length) {
      rows.push(rows_nodes.shift().outerHTML);
    }
    return rows;
  },
  // get tag name, content tag name, tag height, calc cluster height
  exploreEnvironment: function(rows, cache) {
    let opts = this.options;
    opts.content_tag = this.content_elem.tagName.toLowerCase();
    if( ! rows.length) return;
    if(ie && ie <= 9 && ! opts.tag) opts.tag = rows[0].match(/<([^>\s/]*)/)[1].toLowerCase();
    if(this.content_elem.children.length <= 1) cache.data = this.html(rows[0] + rows[0] + rows[0]);
    if( ! opts.tag) opts.tag = this.content_elem.children[0].tagName.toLowerCase();
    this.getRowsHeight(rows);
  },
  getRowsHeight: function(rows) {
    let opts = this.options,
      prev_item_height = opts.item_height;
    opts.cluster_height = 0;
    if( ! rows.length) return;
    let nodes = this.content_elem.children;
    if( ! nodes.length) return;
    let node = nodes[Math.floor(nodes.length / 2)];
    opts.item_height = node.offsetHeight;
    // consider table's border-spacing
    if(opts.tag == 'tr' && getStyle('borderCollapse', this.content_elem) != 'collapse')
      opts.item_height += parseInt(getStyle('borderSpacing', this.content_elem), 10) || 0;
    // consider margins (and margins collapsing)
    if(opts.tag != 'tr') {
      let marginTop = parseInt(getStyle('marginTop', node), 10) || 0;
      let marginBottom = parseInt(getStyle('marginBottom', node), 10) || 0;
      opts.item_height += Math.max(marginTop, marginBottom);
    }
    opts.block_height = opts.item_height * opts.rows_in_block;
    opts.rows_in_cluster = opts.blocks_in_cluster * opts.rows_in_block;
    opts.cluster_height = opts.blocks_in_cluster * opts.block_height;
    console.log("getRowsHeight"+prev_item_height != opts.item_height);
    return prev_item_height != opts.item_height;
  },
  // get current cluster number
  getClusterNum: function () {
    //let offset = document.querySelector(".article_favorites").offsetHeight + 60;
    //let offset = this.topHeightOffset;


    //console.log("this.topHeightOffset"+self.topHeightOffset);
    //console.log("this.topHeightOffset"+this.options.offset);
    this.options.scroll_top = this.scroll_elem.scrollTop - this.options.offset;
    //console.log("getClusterNum"+this.scroll_elem.scrollTop+":::"+this.options.scroll_top + ":::" +Math.floor(this.options.scroll_top / (this.options.cluster_height - this.options.block_height)) || 0);
    return Math.floor(this.options.scroll_top / (this.options.cluster_height - this.options.block_height)) || 0;
  },
  // generate empty row if no data provided
  generateEmptyRow: function() {
    let opts = this.options;
    if( ! opts.tag || ! opts.show_no_data_row) return [];
    let empty_row = document.createElement(opts.tag),
      no_data_content = document.createTextNode(opts.no_data_text), td;
    empty_row.className = opts.no_data_class;
    if(opts.tag == 'tr') {
      td = document.createElement('td');
      // fixes #53
      td.colSpan = 100;
      td.appendChild(no_data_content);
    }
    empty_row.appendChild(td || no_data_content);
    return [empty_row.outerHTML];
  },
  // generate cluster for current scroll position
  generate: function (rows, cluster_num) {
    let opts = this.options,
      rows_len = rows.length;
    if (rows_len < opts.rows_in_block) {
      return {
        top_offset: 0,
        bottom_offset: 0,
        rows_above: 0,
        rows: rows_len ? rows : this.generateEmptyRow()
      }
    }
    let items_start = Math.max((opts.rows_in_cluster - opts.rows_in_block) * cluster_num, 0),
      items_end = items_start + opts.rows_in_cluster,
      top_offset = Math.max(items_start * opts.item_height, 0),
      bottom_offset = Math.max((rows_len - items_end) * opts.item_height, 0),
      this_cluster_rows = [],
      rows_above = items_start;
      // console.log("items_start"+items_start);
      // console.log("items_end"+items_end);
      // console.log("top_offset"+top_offset);
      // console.log("bottom_offset"+bottom_offset);
      // console.log("rows_above"+rows_above);
      //top_offset = 0;
    if(top_offset < 1) {
      rows_above++;
    }
    for (let i = items_start; i < items_end; i++) {
      rows[i] && this_cluster_rows.push(rows[i]);
    }
    return {
      top_offset: top_offset,
      bottom_offset: bottom_offset,
      rows_above: rows_above,
      rows: this_cluster_rows
    }
  },
  renderExtraTag: function(class_name, height) {
    let tag = document.createElement(this.options.tag),
      clusterize_prefix = 'clusterize-';
    tag.className = [clusterize_prefix + 'extra-row', clusterize_prefix + class_name].join(' ');
    height && (tag.style.height = height + 'px');
    return tag.outerHTML;
  },
  // if necessary verify data changed and insert to DOM
  insertToDOM: function(rows, cache) {
    console.log("insertToDOM");
    // explore row's height
    if( ! this.options.cluster_height) {
      this.exploreEnvironment(rows, cache);
    }
    let data = this.generate(rows, this.getClusterNum()),
      this_cluster_rows = data.rows.join(''),
      this_cluster_content_changed = this.checkChanges('data', this_cluster_rows, cache),
      top_offset_changed = this.checkChanges('top', data.top_offset, cache),
      only_bottom_offset_changed = this.checkChanges('bottom', data.bottom_offset, cache),
      callbacks = this.options.callbacks,
      layout = [];

    if(this_cluster_content_changed || top_offset_changed) {
      if(data.top_offset) {
        this.options.keep_parity && layout.push(this.renderExtraTag('keep-parity'));
        layout.push(this.renderExtraTag('top-space', data.top_offset));
      }
      layout.push(this_cluster_rows);
      data.bottom_offset && layout.push(this.renderExtraTag('bottom-space', data.bottom_offset));
      callbacks.clusterWillChange && callbacks.clusterWillChange();
      this.html(layout.join(''));
      this.options.content_tag == 'ol' && this.content_elem.setAttribute('start', data.rows_above);
      this.content_elem.style['counter-increment'] = 'clusterize-counter ' + (data.rows_above-1);
      callbacks.clusterChanged && callbacks.clusterChanged();
    } else if(only_bottom_offset_changed) {
      this.content_elem.lastChild.style.height = data.bottom_offset + 'px';
    }
  },
  // unfortunately ie <= 9 does not allow to use innerHTML for table elements, so make a workaround
  html: function(data) {
    let content_elem = this.content_elem;
    if(ie && ie <= 9 && this.options.tag == 'tr') {
      let div = document.createElement('div'), last;
      div.innerHTML = '<table><tbody>' + data + '</tbody></table>';
      while((last = content_elem.lastChild)) {
        content_elem.removeChild(last);
      }
      let rows_nodes = this.getChildNodes(div.firstChild.firstChild);
      while (rows_nodes.length) {
        content_elem.appendChild(rows_nodes.shift());
      }
    } else {
      content_elem.innerHTML = data;
    }
  },
  getChildNodes: function(tag) {
      let child_nodes = tag.children, nodes = [];
      for (let i = 0, ii = child_nodes.length; i < ii; i++) {
          nodes.push(child_nodes[i]);
      }
      return nodes;
  },
  checkChanges: function(type, value, cache) {
    let changed = value != cache[type];
    cache[type] = value;
    return changed;
  }
}

// support functions
function on(evt, element, fnc) {
  return element.addEventListener ? element.addEventListener(evt, fnc, false) : element.attachEvent("on" + evt, fnc);
}
function off(evt, element, fnc) {
  return element.removeEventListener ? element.removeEventListener(evt, fnc, false) : element.detachEvent("on" + evt, fnc);
}
function isArray(arr) {
  return Object.prototype.toString.call(arr) === '[object Array]';
}
function getStyle(prop, elem) {
  return window.getComputedStyle ? window.getComputedStyle(elem)[prop] : elem.currentStyle[prop];
}

return Clusterize;
}));