/*

 Name: jQuery sameHeight 1.4.0
 Author: giorgio.beggiora@gmail.com

 requestAnimationFrame polyfill by Erik MĂśller. fixes from Paul Irish and Tino Zijdel
 https://gist.github.com/paulirish/1579671

 Optional dependency: CSS-Element-Queries
 http://marcj.github.io/css-element-queries/

 This plugin sets all the matched elements' height to the highest one,
 waiting for images in them to have their height setted (i.e. when they're loaded).
 It's compatible with elements in a container with the css property column-count
 greater than 1.

 Options:

 - compact (boolean) [default = false] : each row will have its own height.

 - responsive (boolean) [default = true] : if true, the max height depends
 from the elements' content, from css otherwise.

 - target: if an element is given, the height will be its one.

 - debounce: debounce in milliseconds, rounded by requestAnimationFrame.

 - observe (window/DOM element) [default = window]: elements which size changes
   must be detected.

 - columnCount (number/string) [default = 1]: closest ancestor's CSS column-count
   property's value ('auto' is not supported).
   If set to 'calc', each time the resize event is triggered the DOM will be traversed
   upward until an element with a column-count value greater than 1 will be found,
   so it's better to avoid it and explicitly set a number.

Returns:

An instance of the observer with the stop() method to stop listening the resize event (similar to the jQuery's off() method).

 */

if (!Number.isFinite) Number.isFinite = function(value) {
    return typeof value === 'number' && isFinite(value);
}

if (!Number.isInteger) Number.isInteger = function(value) {
    return typeof value === 'number' && isFinite(value) && Math.floor(value) === value;
}

;(function() {

    if (!window.requestAnimationFrame) {
        window.requestAnimationFrame = window.mozRequestAnimationFrame
            || window.webkitRequestAnimationFrame
            || window.msRequestAnimationFrame
            || function(f){return setTimeout(f, 1000/60)}
    }

    if (!window.cancelAnimationFrame) {
        window.cancelAnimationFrame = window.mozCancelAnimationFrame
            || function(requestID){clearTimeout(requestID)}
    }

}());

(function ( $ ) {

    const win = this;
    var eventResizeHandler = null;

    var elementResizeHandlers = [];
    var windowObserverHandlers = null;
    var windowObserverHandlersLen = 0;
    var windowObserverHandlersAI = 0;

    var rAFTimeoutHandlers = {};
    var rAFTimeoutHandlersNextId = 0;
    var windowObserverHandlerId = 0;

    function setRAFTimeout(callback, ms) {

        var id, h, now = Date.now();
        if (arguments.length > 2) {
            id = arguments[2];
            h = rAFTimeoutHandlers[id];
        } else {
            id = rAFTimeoutHandlersNextId++ ;
            h = { id: id, start: now };
            rAFTimeoutHandlers[id] = h;
        }

        if ( h ) {
            if ( now - h.start < ms ) {
                requestAnimationFrame(setRAFTimeout.bind(win, callback, ms, id));
            } else {
                delete rAFTimeoutHandlers[id];
                callback();
            }
        }

        return id;
    }

    function clearRAFTimeout (id) {
        delete rAFTimeoutHandlers[id];
    }

    function sameHeightObserver (options) {

        this.$el = options.$el;
        this.settings = options.settings;

        this.debounceHandler = null; // handler del debouncer
        this.observerHandler = null; // handler dell'observer
        var observeMethod = options.observeMethod;

        if (observeMethod === 'Window') {
            this.windowObserverHandlerId = windowObserverHandlerId;
        }

        this.debouncedResize = debouncedResize.bind(this);

        this.stop = function () {
            var clearHandler = function () {} ;
            switch (observeMethod) {
                case 'Window':
                    delete windowObserverHandlers[this.windowObserverHandlerId];
                    windowObserverHandlersLen -= 1;
                    break;
                case 'ResizeObserver':
                    this.observerHandler.disconnect();
                    break;
                case 'ResizeSensor':
                    this.observerHandler.detach();
                    break;
                case 'requestAnimationFrame':
                    clearRAFTimeout(this.observerHandler);
                    break;
            }
        }

    }

    function debouncedResize() {
        if (this === win) {
            for (id in windowObserverHandlers) {
                var instance = windowObserverHandlers[id];
                clearRAFTimeout(instance.debounceHandler);
                instance.debounceHandler = setRAFTimeout(doTheMagic.bind(instance), instance.settings.debounce);
            }
        } else {
            var instance = this;
            clearRAFTimeout(instance.debounceHandler);
            instance.debounceHandler = setRAFTimeout(doTheMagic.bind(instance), instance.settings.debounce);
        }
    }

    function doTheMagic () {

        var $el = this.$el;
        var settings = this.settings;

        // - Check if asynchronous elements have an height...
        var images = $el.find('img, input[type="image"]').toArray();
        var allImagesHaveHeight = images.every(function(img){
            return !! ( img.complete || img.height || parseInt(getComputedStyle(img).height) );
        });

        // ...If not, continue but schedule another loop.
        if (!allImagesHaveHeight) {
            clearRAFTimeout(this.debounceHandler);
            this.debounceHandler = setRAFTimeout(this.debouncedResize, settings.debounce);
        }

        // - Check if target exists...
        var $target = $(settings.target)

        // ...If yes, use its height, end this loop and schedule another one.
        if($target.length){
            $el.height($target.height());
            clearRAFTimeout(this.debounceHandler);
            this.debounceHandler = setRAFTimeout(this.debouncedResize, settings.debounce);
            return;
        }

        // - Group elements per row
        var groupData = null;

        if (settings.compact) {
            var cssColumns = 1;
            if (typeof settings.columnCount === 'number') {
                cssColumns = settings.columnCount;
            } else if (settings.columnCount === 'calc') {
                var up = $el.get(0).parentNode;
                var columnCount = '';
                do {
                    columnCount = Number(getComputedStyle(up).columnCount);
                    if (Number.isInteger(columnCount) && columnCount > 1) {
                        cssColumns = columnCount;
                        break;
                    }
                    up = up.parentNode;
                } while (up.parentNode);
            }
            groupData = cssColumns > 1 ? groupCompactColumns($el, cssColumns) : groupCompact($el);
        } else {
            groupData = group($el);
        }

        // - Calculate heights
        calcHeights($el, groupData, settings);

    } // end doTheMagic()

    function group ($el) {
        var el_length = $el.length;
        var grouped = [];
        var cols = el_length;
        var rows = 1;
        grouped[0] = [];
        for (var i = 0; i < el_length; i++) {
            grouped[0].push($el.get(i));
        }
        return {grouped: grouped, rows: rows, cols: cols};
    }

    function groupCompact ($el) {
        var el_length = $el.length;
        var grouped = [];
        var y = null;
        for(var cols = 0; cols < el_length; cols++){
            var offset = $el.eq(cols).offset();
            var offsetTop = offset.top;
            if(y !== offsetTop){
                if(y === null){
                    y = offsetTop;
                }else{
                    break;
                }
            }
        }
        rows = Math.ceil(el_length / cols);
        for(i = 0; i < el_length; i++){
            r = Math.floor(i / cols);
            c = i % cols;
            if(typeof grouped[r] === 'undefined'){
                grouped[r] = [];
            }
            grouped[r].push($el.get(i));
        }
        return {grouped: grouped, rows: rows, cols: cols};
    }

    function groupCompactColumns ($el, cols) {
        var el_length = $el.length;
        var grouped = [];
        var rows = Math.ceil(el_length / cols);
        for(var i = 0; i < el_length; i++){
            var r = i % rows;
            var c = Math.floor(i / rows);
            if(typeof grouped[r] === 'undefined'){
                grouped[r] = [];
            }
            grouped[r].push($el.get(i));
        }
        return {grouped: grouped, rows: rows, cols: cols};
    }

    function calcHeights ($el, groupData, settings) {
        var grouped = groupData.grouped;
        var rows = groupData.rows;
        for(var r = 0; r < rows; r++){
            var maxHeight = 0;
            var g = grouped[r], gl = g.length;
            for(var c = 0; c < gl; c++){
                var item = g[c], $item = $(item);
                if(settings.responsive){
                    item.style.height = 'auto';
                }
                maxHeight = Math.max(maxHeight, $item.height());
            }
            $(g).height(maxHeight);
        }
    }

    function sameHeight ( options ) {
        var settings = $.extend({
            compact: false,
            responsive: true,
            target: null,
            observe: window,
            debounce: 1,
            columnCount: 1
        }, options );

        if ($.isNumeric(settings.debounce)){
            settings.debounce = Number(settings.debounce);
            if (settings.debounce < 1) settings.debounce = 1;
        } else {
            settings.debounce = 1;
        }

        if ($.isNumeric(settings.columnCount)) {
            settings.columnCount = Number(settings.columnCount);
            if (settings.columnCount < 1) settings.columnCount = 1;
        } else {
            if (settings.columnCount !== 'calc') settings.columnCount = 1;
        }

        var $el = $(this);
        var newInstanceOptions = {
            $el: $el,
            settings: settings
        };

        var observeWhat = '';
        var observeType = Object.prototype.toString.call( settings.observe );

        if ( observeType === '[object String]' ) {
            observeWhat = 'query';
        } if ( observeType === '[object Window]' ) {
            observeWhat = 'window';
        } else if ( observeType === '[object HTMLCollection]' ) {
            observeWhat = 'element';
        } else if ( observeType.indexOf('[object HTML') === 0 ) {
            observeWhat = 'element';
        }

        var observeMethod = '';
        if (observeWhat === 'window') {
            observeMethod = 'Window';
        } else  if(observeWhat) {
            if ('ResizeObserver' in window) {
                observeMethod = 'ResizeObserver';
            } else if ('ResizeSensor' in window) {
                observeMethod = 'ResizeSensor';
            } else {
                observeMethod = 'requestAnimationFrame';
            }
        }

        newInstanceOptions.observeMethod = observeMethod;
        var newInstance = new sameHeightObserver(newInstanceOptions);

        switch (observeMethod) {
            case 'Window':
                if (windowObserverHandlers === null) {
                    windowObserverHandlers = {};
                    var $win = $(window);
                    $win.on('resize', debouncedResize);
                    $win.on('load', debouncedResize);
                }
                windowObserverHandlerId = windowObserverHandlersAI++;
                windowObserverHandlers[windowObserverHandlerId] = newInstance;
                windowObserverHandlersLen += 1;
                break;
            case 'ResizeObserver':
                var ro = new ResizeObserver(newInstance.debouncedResize);
                ro.observe(settings.observe);
                newInstance.observerHandler = ro;
                break;
            case 'ResizeSensor':
                var rs = new ResizeSensor(settings.observe, newInstance.debouncedResize);
                newInstance.observerHandler = rs;
                break;
            case 'requestAnimationFrame':
                var oldw = [], oldh = [];
                (function resizeLoop(instance){
                    var neww = [], newh = [];
                    var settings = newInstance.settings;
                    newInstance.$el.each(function(i, element){
                        var $element = $(element);
                        neww.push($element.width());
                        newh.push($element.height());
                    });
                    var i = neww.length;
                    while(i--){
                        if( neww[i] !== oldw[i] || newh[i] !== oldh[i] ){
                            oldw = neww;
                            oldh = newh;
                            newInstance.debouncedResize();
                            break;
                        }
                    }
                    newInstance.observerHandler = setRAFTimeout(resizeLoop, settings.debounce);
                })();
                break;
        }

        newInstance.debouncedResize();

        // return instance controller

        return newInstance;

    } // end sameHeight()

    $.fn.sameHeight = sameHeight;

}( jQuery ));