/**
* Copyright Marc J. Schmidt. See the LICENSE file at the top-level
* directory of this distribution and at
* https://github.com/marcj/css-element-queries/blob/master/LICENSE.
*/
;
(function (root, factory) {
   if (typeof define === "function" && define.amd) {
       define(factory);
   } else if (typeof exports === "object") {
       module.exports = factory();
   } else {
       root.ResizeSensor = factory();
   }
}(typeof window !== 'undefined' ? window : this, function () {

   // Make sure it does not throw in a SSR (Server Side Rendering) situation
   if (typeof window === "undefined") {
       return null;
   }
   // Only used for the dirty checking, so the event callback count is limited to max 1 call per fps per sensor.
   // In combination with the event based resize sensor this saves cpu time, because the sensor is too fast and
   // would generate too many unnecessary events.
   var requestAnimationFrame = window.requestAnimationFrame ||
       window.mozRequestAnimationFrame ||
       window.webkitRequestAnimationFrame ||
       function (fn) {
           return window.setTimeout(fn, 20);
       };

   /**
    * Iterate over each of the provided element(s).
    *
    * @param {HTMLElement|HTMLElement[]} elements
    * @param {Function}                  callback
    */
   function forEachElement(elements, callback){
       var elementsType = Object.prototype.toString.call(elements);
       var isCollectionTyped = ('[object Array]' === elementsType
           || ('[object NodeList]' === elementsType)
           || ('[object HTMLCollection]' === elementsType)
           || ('[object Object]' === elementsType)
           || ('undefined' !== typeof jQuery && elements instanceof jQuery) //jquery
           || ('undefined' !== typeof Elements && elements instanceof Elements) //mootools
       );
       var i = 0, j = elements.length;
       if (isCollectionTyped) {
           for (; i < j; i++) {
               callback(elements[i]);
           }
       } else {
           callback(elements);
       }
   }

   /**
    * Class for dimension change detection.
    *
    * @param {Element|Element[]|Elements|jQuery} element
    * @param {Function} callback
    *
    * @constructor
    */
   var ResizeSensor = function(element, callback) {
       /**
        *
        * @constructor
        */
       function EventQueue() {
           var q = [];
           this.add = function(ev) {
               q.push(ev);
           };

           var i, j;
           this.call = function() {
               for (i = 0, j = q.length; i < j; i++) {
                   q[i].call();
               }
           };

           this.remove = function(ev) {
               var newQueue = [];
               for(i = 0, j = q.length; i < j; i++) {
                   if(q[i] !== ev) newQueue.push(q[i]);
               }
               q = newQueue;
           }

           this.length = function() {
               return q.length;
           }
       }

       /**
        *
        * @param {HTMLElement} element
        * @param {Function}    resized
        */
       function attachResizeEvent(element, resized) {
           if (!element) return;
           if (element.resizedAttached) {
               element.resizedAttached.add(resized);
               return;
           }

           element.resizedAttached = new EventQueue();
           element.resizedAttached.add(resized);

           element.resizeSensor = document.createElement('div');
           element.resizeSensor.className = 'resize-sensor';
           var style = 'position: absolute; left: 0; top: 0; right: 0; bottom: 0; overflow: hidden; z-index: -1; visibility: hidden;';
           var styleChild = 'position: absolute; left: 0; top: 0; transition: 0s;';

           element.resizeSensor.style.cssText = style;
           element.resizeSensor.innerHTML =
               '<div class="resize-sensor-expand" style="' + style + '">' +
                   '<div style="' + styleChild + '"></div>' +
               '</div>' +
               '<div class="resize-sensor-shrink" style="' + style + '">' +
                   '<div style="' + styleChild + ' width: 200%; height: 200%"></div>' +
               '</div>';
           element.appendChild(element.resizeSensor);

           if (element.resizeSensor.offsetParent !== element) {
               element.style.position = 'relative';
           }

           var expand = element.resizeSensor.childNodes[0];
           var expandChild = expand.childNodes[0];
           var shrink = element.resizeSensor.childNodes[1];
           var dirty, rafId, newWidth, newHeight;
           var lastWidth = element.offsetWidth;
           var lastHeight = element.offsetHeight;

           var reset = function() {
               expandChild.style.width = '100000px';
               expandChild.style.height = '100000px';

               expand.scrollLeft = 100000;
               expand.scrollTop = 100000;

               shrink.scrollLeft = 100000;
               shrink.scrollTop = 100000;
           };

           reset();

           var onResized = function() {
               rafId = 0;

               if (!dirty) return;

               lastWidth = newWidth;
               lastHeight = newHeight;

               if (element.resizedAttached) {
                   element.resizedAttached.call();
               }
           };

           var onScroll = function() {
               newWidth = element.offsetWidth;
               newHeight = element.offsetHeight;
               dirty = newWidth != lastWidth || newHeight != lastHeight;

               if (dirty && !rafId) {
                   rafId = requestAnimationFrame(onResized);
               }

               reset();
           };

           var addEvent = function(el, name, cb) {
               if (el.attachEvent) {
                   el.attachEvent('on' + name, cb);
               } else {
                   el.addEventListener(name, cb);
               }
           };

           addEvent(expand, 'scroll', onScroll);
           addEvent(shrink, 'scroll', onScroll);
       }

       forEachElement(element, function(elem){
           attachResizeEvent(elem, callback);
       });

       this.detach = function(ev) {
           ResizeSensor.detach(element, ev);
       };
   };

   ResizeSensor.detach = function(element, ev) {
       forEachElement(element, function(elem){
           if (!elem) return
           if(elem.resizedAttached && typeof ev == "function"){
               elem.resizedAttached.remove(ev);
               if(elem.resizedAttached.length()) return;
           }
           if (elem.resizeSensor) {
               if (elem.contains(elem.resizeSensor)) {
                   elem.removeChild(elem.resizeSensor);
               }
               delete elem.resizeSensor;
               delete elem.resizedAttached;
           }
       });
   };

   return ResizeSensor;

}));