const Lightbox = (($) => {

const NAME = 'ekkoLightbox'
const JQUERY_NO_CONFLICT = $.fn[NAME]
const Default = {
        title: '',
        footer: '',
        maxWidth: 9999,
        maxHeight: 9999,
        showArrows: true, //display the left / right arrows or not
        wrapping: true, //if true, gallery loops infinitely
        type: null, //force the lightbox into image / youtube mode. if null, or not image|youtube|vimeo; detect it
        alwaysShowClose: false, //always show the close button, even if there is no title
        loadingMessage: '<div class="ekko-lightbox-loader"><div><div></div><div></div></div></div>', // http://tobiasahlin.com/spinkit/
        leftArrow: '<span>&#10094;</span>',
        rightArrow: '<span>&#10095;</span>',
        strings: {
                close: 'Close',
                fail: 'Failed to load image:',
                type: 'Could not detect remote target type. Force the type using data-type',
        },
        doc: document, // if in an iframe can specify top.document
        onShow() {},
        onShown() {},
        onHide() {},
        onHidden() {},
        onNavigate() {},
        onContentLoaded() {}
}
class Lightbox {
        /**
    Class properties:
         _$element: null -> the <a> element currently being displayed
         _$modal: The bootstrap modal generated
            _$modalDialog: The .modal-dialog
            _$modalContent: The .modal-content
            _$modalBody: The .modal-body
            _$modalHeader: The .modal-header
            _$modalFooter: The .modal-footer
         _$lightboxContainerOne: Container of the first lightbox element
         _$lightboxContainerTwo: Container of the second lightbox element
         _$lightboxBody: First element in the container
         _$modalArrows: The overlayed arrows container
         _$galleryItems: Other <a>'s available for this gallery
         _galleryName: Name of the current data('gallery') showing
         _galleryIndex: The current index of the _$galleryItems being shown
         _config: {} the options for the modal
         _modalId: unique id for the current lightbox
         _padding / _border: CSS properties for the modal container; these are used to calculate the available space for the content
         */
        static get Default() {
                return Default
        }
        constructor($element, config) {
                this._config = $.extend({}, Default, config)
                this._$modalArrows = null
                this._galleryIndex = 0
                this._galleryName = null
                this._padding = null
                this._border = null
                this._titleIsShown = false
                this._footerIsShown = false
                this._wantedWidth = 0
                this._wantedHeight = 0
                this._touchstartX = 0
                this._touchendX = 0
                this._modalId = `ekkoLightbox-${Math.floor((Math.random() * 1000) + 1)}`;
                this._$element = $element instanceof jQuery ? $element : $($element)
                this._isBootstrap3 = $.fn.modal.Constructor.VERSION[0] == 3;
                let h4 = `<h4 class="modal-title">${this._config.title || "&nbsp;"}</h4>`;
                let btn = `<button type="button" class="close" data-dismiss="modal" aria-label="${this._config.strings.close}"><span aria-hidden="true">&times;</span></button>`;
                let header = `<div class="modal-header${this._config.title || this._config.alwaysShowClose ? '' : ' hide'}">`+(this._isBootstrap3 ? btn+h4 : h4+btn)+`</div>`;
                let footer = `<div class="modal-footer${this._config.footer ? '' : ' hide'}">${this._config.footer || "&nbsp;"}</div>`;
                let body = '<div class="modal-body"><div class="ekko-lightbox-container"><div class="ekko-lightbox-item fade in show"></div><div class="ekko-lightbox-item fade"></div></div></div>'
                let dialog = `<div class="modal-dialog" role="document"><div class="modal-content">${header}${body}${footer}</div></div>`
                $(this._config.doc.body).append(`<div id="${this._modalId}" class="ekko-lightbox modal fade" tabindex="-1" tabindex="-1" role="dialog" aria-hidden="true">${dialog}</div>`)
                this._$modal = $(`#${this._modalId}`, this._config.doc)
                this._$modalDialog = this._$modal.find('.modal-dialog').first()
                this._$modalContent = this._$modal.find('.modal-content').first()
                this._$modalBody = this._$modal.find('.modal-body').first()
                this._$modalHeader = this._$modal.find('.modal-header').first()
                this._$modalFooter = this._$modal.find('.modal-footer').first()
                this._$lightboxContainer = this._$modalBody.find('.ekko-lightbox-container').first()
                this._$lightboxBodyOne = this._$lightboxContainer.find('> div:first-child').first()
                this._$lightboxBodyTwo = this._$lightboxContainer.find('> div:last-child').first()
                this._border = this._calculateBorders()
                this._padding = this._calculatePadding()
                this._galleryName = this._$element.data('gallery')
                if (this._galleryName) {
                        this._$galleryItems = $(document.body).find(`*[data-gallery="${this._galleryName}"]`)
                        this._galleryIndex = this._$galleryItems.index(this._$element)
                        $(document).on('keydown.ekkoLightbox', this._navigationalBinder.bind(this))
                        // add the directional arrows to the modal
                        if (this._config.showArrows && this._$galleryItems.length > 1) {
                                this._$lightboxContainer.append(`<div class="ekko-lightbox-nav-overlay"><a href="#">${this._config.leftArrow}</a><a href="#">${this._config.rightArrow}</a></div>`)
                                this._$modalArrows = this._$lightboxContainer.find('div.ekko-lightbox-nav-overlay').first()
                                this._$lightboxContainer.on('click', 'a:first-child', event => {
                                        event.preventDefault()
                                        return this.navigateLeft()
                                })
                                this._$lightboxContainer.on('click', 'a:last-child', event => {
                                        event.preventDefault()
                                        return this.navigateRight()
                                })
                                this.updateNavigation()
                        }
                }
                this._$modal
                .on('show.bs.modal', this._config.onShow.bind(this))
                .on('shown.bs.modal', () => {
                        this._toggleLoading(true)
                        this._handle()
                        return this._config.onShown.call(this)
                })
                .on('hide.bs.modal', this._config.onHide.bind(this))
                .on('hidden.bs.modal', () => {
                        if (this._galleryName) {
                                $(document).off('keydown.ekkoLightbox')
                                $(window).off('resize.ekkoLightbox')
                        }
                        this._$modal.remove()
                        return this._config.onHidden.call(this)
                })
                .modal(this._config)
                $(window).on('resize.ekkoLightbox', () => {
                        this._resize(this._wantedWidth, this._wantedHeight)
                })
                this._$lightboxContainer
                .on('touchstart', () => {
                        this._touchstartX = event.changedTouches[0].screenX;
                })
                .on('touchend', () => {
                        this._touchendX = event.changedTouches[0].screenX;
                    this._swipeGesure();
                })
        }
        element() {
                return this._$element;
        }
        modal() {
                return this._$modal;
        }
        navigateTo(index) {
                if (index < 0 || index > this._$galleryItems.length-1)
                        return this
                this._galleryIndex = index
                this.updateNavigation()
                this._$element = $(this._$galleryItems.get(this._galleryIndex))
                this._handle();
        }
        navigateLeft() {
                if(!this._$galleryItems)
                        return;
                if (this._$galleryItems.length === 1)
                        return
                if (this._galleryIndex === 0) {
                        if (this._config.wrapping)
                                this._galleryIndex = this._$galleryItems.length - 1
                        else
                                return
                }
                else //circular
                        this._galleryIndex--
                this._config.onNavigate.call(this, 'left', this._galleryIndex)
                return this.navigateTo(this._galleryIndex)
        }
        navigateRight() {
                if(!this._$galleryItems)
                        return;
                if (this._$galleryItems.length === 1)
                        return
                if (this._galleryIndex === this._$galleryItems.length - 1) {
                        if (this._config.wrapping)
                                this._galleryIndex = 0
                        else
                                return
                }
                else //circular
                        this._galleryIndex++
                this._config.onNavigate.call(this, 'right', this._galleryIndex)
                return this.navigateTo(this._galleryIndex)
        }
        updateNavigation() {
                if (!this._config.wrapping) {
                        let $nav = this._$lightboxContainer.find('div.ekko-lightbox-nav-overlay')
                        if (this._galleryIndex === 0)
                                $nav.find('a:first-child').addClass('disabled')
                        else
                                $nav.find('a:first-child').removeClass('disabled')
                        if (this._galleryIndex === this._$galleryItems.length - 1)
                                $nav.find('a:last-child').addClass('disabled')
                        else
                                $nav.find('a:last-child').removeClass('disabled')
                }
        }
        close() {
                return this._$modal.modal('hide');
        }
        // helper private methods
        _navigationalBinder(event) {
                event = event || window.event;
                if (event.keyCode === 39)
                        return this.navigateRight()
                if (event.keyCode === 37)
                        return this.navigateLeft()
        }
        // type detection private methods
        _detectRemoteType(src, type) {
                type = type || false;
                if(!type && this._isImage(src))
                        type = 'image';
                if(!type && this._getYoutubeId(src))
                        type = 'youtube';
                if(!type && this._getVimeoId(src))
                        type = 'vimeo';
                if(!type && this._getInstagramId(src))
                        type = 'instagram';
                if(!type || ['image', 'youtube', 'vimeo', 'instagram', 'video', 'url'].indexOf(type) < 0)
                        type = 'url';
                return type;
        }
        _isImage(string) {
                return string && string.match(/(^data:image\/.*,)|(\.(jp(e|g|eg)|gif|png|bmp|webp|svg)((\?|#).*)?$)/i)
        }
        _containerToUse() {
                // if currently showing an image, fade it out and remove
                let $toUse = this._$lightboxBodyTwo
                let $current = this._$lightboxBodyOne
                if(this._$lightboxBodyTwo.hasClass('in')) {
                        $toUse = this._$lightboxBodyOne
                        $current = this._$lightboxBodyTwo
                }
                $current.removeClass('in show')
                setTimeout(() => {
                        if(!this._$lightboxBodyTwo.hasClass('in'))
                                this._$lightboxBodyTwo.empty()
                        if(!this._$lightboxBodyOne.hasClass('in'))
                                this._$lightboxBodyOne.empty()
                }, 500)
                $toUse.addClass('in show')
                return $toUse
        }
        _handle() {
                let $toUse = this._containerToUse()
                this._updateTitleAndFooter()
                let currentRemote = this._$element.attr('data-remote') || this._$element.attr('href')
                let currentType = this._detectRemoteType(currentRemote, this._$element.attr('data-type') || false)
                if(['image', 'youtube', 'vimeo', 'instagram', 'video', 'url'].indexOf(currentType) < 0)
                        return this._error(this._config.strings.type)
                switch(currentType) {
                        case 'image':
                                this._preloadImage(currentRemote, $toUse)
                                this._preloadImageByIndex(this._galleryIndex, 3)
                                break;
                        case 'youtube':
                                this._showYoutubeVideo(currentRemote, $toUse);
                                break;
                        case 'vimeo':
                                this._showVimeoVideo(this._getVimeoId(currentRemote), $toUse);
                                break;
                        case 'instagram':
                                this._showInstagramVideo(this._getInstagramId(currentRemote), $toUse);
                                break;
                        case 'video':
                                this._showHtml5Video(currentRemote, $toUse);
                                break;
                        default: // url
                                this._loadRemoteContent(currentRemote, $toUse);
                                break;
                }
                return this;
        }
        _getYoutubeId(string) {
                if(!string)
                        return false;
                let matches = string.match(/^.*(youtu.be\/|v\/|u\/\w\/|embed\/|watch\?v=|\&v=)([^#\&\?]*).*/)
                return (matches && matches[2].length === 11) ? matches[2] : false
        }
        _getVimeoId(string) {
                return string && string.indexOf('vimeo') > 0 ? string : false
        }
        _getInstagramId(string) {
                return string && string.indexOf('instagram') > 0 ? string : false
        }
        // layout private methods
        _toggleLoading(show) {
                show = show || false
                if(show) {
                        this._$modalDialog.css('display', 'none')
                        this._$modal.removeClass('in show')
                        $('.modal-backdrop').append(this._config.loadingMessage)
                }
                else {
                        this._$modalDialog.css('display', 'block')
                        this._$modal.addClass('in show')
                        $('.modal-backdrop').find('.ekko-lightbox-loader').remove()
                }
                return this;
        }
        _calculateBorders() {
                return {
                        top: this._totalCssByAttribute('border-top-width'),
                        right: this._totalCssByAttribute('border-right-width'),
                        bottom: this._totalCssByAttribute('border-bottom-width'),
                        left: this._totalCssByAttribute('border-left-width'),
                }
        }
        _calculatePadding() {
                return {
                        top: this._totalCssByAttribute('padding-top'),
                        right: this._totalCssByAttribute('padding-right'),
                        bottom: this._totalCssByAttribute('padding-bottom'),
                        left: this._totalCssByAttribute('padding-left'),
                }
        }
        _totalCssByAttribute(attribute) {
                return parseInt(this._$modalDialog.css(attribute), 10) +
                        parseInt(this._$modalContent.css(attribute), 10) +
                        parseInt(this._$modalBody.css(attribute), 10)
        }
        _updateTitleAndFooter() {
                let title = this._$element.data('title') || ""
                let caption = this._$element.data('footer') || ""
                this._titleIsShown = false
                if (title || this._config.alwaysShowClose) {
                        this._titleIsShown = true
                        this._$modalHeader.css('display', '').find('.modal-title').html(title || "&nbsp;")
                }
                else
                        this._$modalHeader.css('display', 'none')
                this._footerIsShown = false
                if (caption) {
                        this._footerIsShown = true
                        this._$modalFooter.css('display', '').html(caption)
                }
                else
                        this._$modalFooter.css('display', 'none')
                return this;
        }
        _showYoutubeVideo(remote, $containerForElement) {
                let id = this._getYoutubeId(remote)
                let query = remote.indexOf('&') > 0 ? remote.substr(remote.indexOf('&')) : ''
                let width = this._$element.data('width') || 560
                let height = this._$element.data('height') ||  width / ( 560/315 )
                return this._showVideoIframe(
                        `//www.youtube.com/embed/${id}?badge=0&autoplay=1&html5=1${query}`,
                        width,
                        height,
                        $containerForElement
                );
        }
        _showVimeoVideo(id, $containerForElement) {
                let width = this._$element.data('width') || 500
                let height = this._$element.data('height') ||  width / ( 560/315 )
                return this._showVideoIframe(id + '?autoplay=1', width, height, $containerForElement)
        }
        _showInstagramVideo(id, $containerForElement) {
                // instagram load their content into iframe's so this can be put straight into the element
                let width = this._$element.data('width') || 612
                let height = width + 80;
                id = id.substr(-1) !== '/' ? id + '/' : id; // ensure id has trailing slash
                $containerForElement.html(`<iframe width="${width}" height="${height}" src="${id}embed/" frameborder="0" allowfullscreen></iframe>`);
                this._resize(width, height);
                this._config.onContentLoaded.call(this);
                if (this._$modalArrows) //hide the arrows when showing video
                        this._$modalArrows.css('display', 'none');
                this._toggleLoading(false);
                return this;
        }
        _showVideoIframe(url, width, height, $containerForElement) { // should be used for videos only. for remote content use loadRemoteContent (data-type=url)
                height = height || width; // default to square
                $containerForElement.html(`<div class="embed-responsive embed-responsive-16by9"><iframe width="${width}" height="${height}" src="${url}" frameborder="0" allowfullscreen class="embed-responsive-item"></iframe></div>`);
                this._resize(width, height);
                this._config.onContentLoaded.call(this);
                if (this._$modalArrows)
                        this._$modalArrows.css('display', 'none'); //hide the arrows when showing video
                this._toggleLoading(false);
                return this;
        }
        _showHtml5Video(url, $containerForElement) { // should be used for videos only. for remote content use loadRemoteContent (data-type=url)
                let width = this._$element.data('width') || 560
                let height = this._$element.data('height') ||  width / ( 560/315 )
                $containerForElement.html(`<div class="embed-responsive embed-responsive-16by9"><video width="${width}" height="${height}" src="${url}" preload="auto" autoplay controls class="embed-responsive-item"></video></div>`);
                this._resize(width, height);
                this._config.onContentLoaded.call(this);
                if (this._$modalArrows)
                        this._$modalArrows.css('display', 'none'); //hide the arrows when showing video
                this._toggleLoading(false);
                return this;
        }
        _loadRemoteContent(url, $containerForElement) {
                let width = this._$element.data('width') || 560;
                let height = this._$element.data('height') || 560;
                let disableExternalCheck = this._$element.data('disableExternalCheck') || false;
                this._toggleLoading(false);
                // external urls are loading into an iframe
                // local ajax can be loaded into the container itself
                if (!disableExternalCheck && !this._isExternal(url)) {
                        $containerForElement.load(url, $.proxy(() => {
                                return this._$element.trigger('loaded.bs.modal');l
                        }));
                } else {
                        $containerForElement.html(`<iframe src="${url}" frameborder="0" allowfullscreen></iframe>`);
                        this._config.onContentLoaded.call(this);
                }
                if (this._$modalArrows) //hide the arrows when remote content
                        this._$modalArrows.css('display', 'none')
                this._resize(width, height);
                return this;
        }
        _isExternal(url) {
                let match = url.match(/^([^:\/?#]+:)?(?:\/\/([^\/?#]*))?([^?#]+)?(\?[^#]*)?(#.*)?/);
                if (typeof match[1] === "string" && match[1].length > 0 && match[1].toLowerCase() !== location.protocol)
                        return true;
                if (typeof match[2] === "string" && match[2].length > 0 && match[2].replace(new RegExp(`:(${{
                                "http:": 80,
                                "https:": 443
                        }[location.protocol]})?$`), "") !== location.host)
                        return true;
                return false;
        }
        _error( message ) {
                console.error(message);
                this._containerToUse().html(message);
                this._resize(300, 300);
                return this;
        }
        _preloadImageByIndex(startIndex, numberOfTimes) {
                if(!this._$galleryItems)
                        return;
                let next = $(this._$galleryItems.get(startIndex), false)
                if(typeof next == 'undefined')
                        return
                let src = next.attr('data-remote') || next.attr('href')
                if (next.attr('data-type') === 'image' || this._isImage(src))
                        this._preloadImage(src, false)
                if(numberOfTimes > 0)
                        return this._preloadImageByIndex(startIndex + 1, numberOfTimes-1);
        }
        _preloadImage( src, $containerForImage) {
                $containerForImage = $containerForImage || false
                let img = new Image();
                if ($containerForImage) {
                        // if loading takes > 200ms show a loader
                        let loadingTimeout = setTimeout(() => {
                                $containerForImage.append(this._config.loadingMessage)
                        }, 200)
                        img.onload = () => {
                                if(loadingTimeout)
                                        clearTimeout(loadingTimeout)
                                loadingTimeout = null;
                                let image = $('<img />');
                                image.attr('src', img.src);
                                image.addClass('img-fluid');
                                // backward compatibility for bootstrap v3
                                image.css('width', '100%');
                                $containerForImage.html(image);
                                if (this._$modalArrows)
                                        this._$modalArrows.css('display', '') // remove display to default to css property
                                this._resize(img.width, img.height);
                                this._toggleLoading(false);
                                return this._config.onContentLoaded.call(this);
                        };
                        img.onerror = () => {
                                this._toggleLoading(false);
                                return this._error(this._config.strings.fail+`  ${src}`);
                        };
                }
                img.src = src;
                return img;
        }
        _swipeGesure() {
            if (this._touchendX < this._touchstartX) {
                return this.navigateRight();
            }
            if (this._touchendX > this._touchstartX) {
                return this.navigateLeft();
            }
        }
        _resize( width, height ) {
                height = height || width
                this._wantedWidth = width
                this._wantedHeight = height
                let imageAspecRatio = width / height;
                // if width > the available space, scale down the expected width and height
                let widthBorderAndPadding = this._padding.left + this._padding.right + this._border.left + this._border.right
                // force 10px margin if window size > 575px
                let addMargin = this._config.doc.body.clientWidth > 575 ? 20 : 0
                let discountMargin = this._config.doc.body.clientWidth > 575 ? 0 : 20
                let maxWidth = Math.min(width + widthBorderAndPadding, this._config.doc.body.clientWidth - addMargin, this._config.maxWidth)
                if((width + widthBorderAndPadding) > maxWidth) {
                        height = (maxWidth - widthBorderAndPadding - discountMargin) / imageAspecRatio;
                        width = maxWidth
                } else
                        width = (width + widthBorderAndPadding)
                let headerHeight = 0,
                    footerHeight = 0
                // as the resize is performed the modal is show, the calculate might fail
                // if so, default to the default sizes
                if (this._footerIsShown)
                        footerHeight = this._$modalFooter.outerHeight(true) || 55
                if (this._titleIsShown)
                        headerHeight = this._$modalHeader.outerHeight(true) || 67
                let borderPadding = this._padding.top + this._padding.bottom + this._border.bottom + this._border.top
                //calculated each time as resizing the window can cause them to change due to Bootstraps fluid margins
                let margins = parseFloat(this._$modalDialog.css('margin-top')) + parseFloat(this._$modalDialog.css('margin-bottom'));
                let maxHeight = Math.min(height, $(window).height() - borderPadding - margins - headerHeight - footerHeight, this._config.maxHeight - borderPadding - headerHeight - footerHeight);
                if(height > maxHeight) {
                        // if height > the available height, scale down the width
                        width = Math.ceil(maxHeight * imageAspecRatio) + widthBorderAndPadding;
                }
                this._$lightboxContainer.css('height', maxHeight)
                this._$modalDialog.css('flex', 1).css('maxWidth', width);
                let modal = this._$modal.data('bs.modal');
                if (modal) {
                        // v4 method is mistakenly protected
                        try {
                                modal._handleUpdate();
                        } catch(Exception) {
                                modal.handleUpdate();
                        }
                }
                return this;
        }
        static _jQueryInterface(config) {
                config = config || {}
                return this.each(() => {
                        let $this = $(this)
                        let _config = $.extend(
                                {},
                                Lightbox.Default,
                                $this.data(),
                                typeof config === 'object' && config
                        )
                        new Lightbox(this, _config)
                })
        }
}
$.fn[NAME]             = Lightbox._jQueryInterface
$.fn[NAME].Constructor = Lightbox
$.fn[NAME].noConflict  = () => {
        $.fn[NAME] = JQUERY_NO_CONFLICT
        return Lightbox._jQueryInterface
}
return Lightbox

})(jQuery)

export default Lightbox