(function($) {
    // distinct list of all document IDs to be fetched via AJAX requests
    var ids;

    // current index in ids array
    var i;

    // whether we're in the visual editor
    var is_editor;

    // CSS selector to match documents requiring Thumber
    var thumber_exts = ['doc', 'docx', 'ppt', 'pptx', 'xls', 'xlsx', 'pub', 'vsd', 'vsdx'];
    var thumber_exts_sel = '.document-gallery [data-ext="' + thumber_exts.join('"],[data-ext="') + '"]';

    // whether pointer has already been shown
    var thumber_pointer_shown = false;

    // find all document-icons without icons generated and start processing
    $(document).ready(function() {
        is_editor = typeof tinymce !== 'undefined';
        sizeGalleryIcons();
        resetPendingIcons();
        handleVisualEditor();
        registerPaginationHandler();
    });

    /**
     * Sets the size of the gallery icons based on the column count.
     * @param $gallery If given, the target gallery. Otherwise all galleries on page.
     */
    function sizeGalleryIcons($gallery) {
        ($gallery || $('.document-gallery[data-icon-width]')).each(function() {
            var icon_width = $(this).data('icon-width');
            if (typeof icon_width !== 'undefined') {
                $(this).find('.document-icon').width(icon_width + '%');
            }
        });
    }

    /**
     * Handles necessary logic for when we're rendering gallery preview within visual editor.
     */
    function handleVisualEditor() {
        if (is_editor) {
            tinymce.PluginManager.add('dg', function (editor, url) {
                editor.on('LoadContent update.dg undo', function (e) {
                    $(e.target.contentDocument).find('[data-wpview-type="dg"] [data-shortcode]').each(function () {
                        retrieveGallery((typeof $(this).data('shortcode') === 'object' ? $(this).data('shortcode') : JSON.parse(decodeURIComponent($(this).data('shortcode')))), $(this));
                    });
                });
            });
        }
    }

    /**
     * Listen for all pagination clicks in current DOM and any future DOM elements.
     */
    function registerPaginationHandler() {
        $('body').delegate('.dg-paginate-wrapper .paginate a', 'click', function (e) {
            var $target = $(this).closest('.dg-paginate-wrapper');
            var atts = $target.data('shortcode');
            atts['skip'] = 0;
            var split = $(this).attr('href').split('#')[1].split('=');
            if ( split.length >= 2 ) {
                atts['skip'] = atts['limit'] * (split.pop() - 1);
            }

            retrieveGallery(atts, $target, function (gallery) {
                var adminBarHeight = $('#wpadminbar').height() || 0;
                var targetTop = gallery.offset().top - adminBarHeight;

                // scroll to gallery if top is not visible
                if ( $(document).scrollTop() > targetTop ) {
                    $('html, body').animate({
                        scrollTop: targetTop - 20
                    }, 'slow');
                }
            });

            e.preventDefault();
        });
    }

    /**
     * Collects all of the DG icons that need to be generated and starts requesting them via AJAX.
     */
    function resetPendingIcons() {
        ids = [];
        i = 0;

        $('.document-gallery img[data-id]').each(function() {
            var id = $(this).data('id');

            // if we have multiple galleries, we could have multiple elements
            // needing the same icon and no need to request multiple times
            if (-1 === $.inArray(id, ids)) {
                ids.push(id);
            }
        });

        retrieveNextIcons();
    }

    /**
     * Requests a gallery generated with the given attributes to populate the given target element.
     * @param atts array The gallery shortcode attributes.
     * @param $target element The element to be updated with the AJAX HTML response.
     * @param callback function If provided, will be invoked once new gallery content is loaded with the updated element passed in.
     */
    function retrieveGallery(atts, $target, callback) {
        // TODO: Cache already-retrieved gallery pages. Need to be careful not to keep too many at a time
        // (could consume a lot of memory) & handle caching pages for multiple galleries on a single pages.
        if ( typeof atts['id'] === 'undefined' ) {
            atts['id'] = wp.media.dgDefaults.id;
        }

        // request new gallery page from server
        $.post(ajaxurl, { action: 'dg_generate_gallery', atts: atts }, function(html) {
            var $parsedHtml = $($.parseHTML(html));
            if (is_editor && !thumber_pointer_shown && $parsedHtml.find(thumber_exts_sel).length) {
                thumber_pointer_shown = true;
                $('#insert-media-button').trigger('ready.dg');
            }

            // keep old ID
            var targetId = $target.attr('id');
            $parsedHtml.attr('id', targetId);

            // update gallery element with new content
            $target.replaceWith($parsedHtml);
            $target = $('#' + targetId);
            sizeGalleryIcons($target);
            resetPendingIcons();

            // invoke callback if provided
            if ( typeof callback !== 'undefined' ) {
                // get the new DOM element
                callback($target);
            }
        });
    }

    /**
     * Sends AJAX request to server requesting some of the not-yet-generated icons (if any).
     */
    function retrieveNextIcons() {
        // max number of icons to retrieve per AJAX request
        var batchLimit = 4;

        // IDs already retrieved
        var idBatch = [];

        for (; i < ids.length; i++) {
            if (idBatch.length === batchLimit) {
                break;
            }

            idBatch.push(ids[i]);
        }

        if (idBatch.length !== 0) {
            // request the next batch of icons
            $.ajax({
                type:     'POST',
                url:      ajaxurl,
                dataType: 'json',
                data:     { action: 'dg_generate_icons', ids: idBatch },
                success:  processRetrievedThumbnails,
                error:    function(xhr) { console.error(xhr.responseText); }
            });
        }
    }

    /**
     * Find all of the relevant elements and set the img src, then start next batch of thumbnail retrieval.
     * @param response Associative array mapping attachment ID to thumbnail URL.
     */
    function processRetrievedThumbnails(response) {
        for (var id in response) {
            if (response.hasOwnProperty(id)) {
                var $target = $('.document-gallery img[data-id="' + id + '"]');
                $target.removeAttr('data-id');

                (function(id, $target) {
                    var speed = 'fast';
                    $target.fadeOut(speed, function () {
                        $(this).attr('src', response[id]);
                        $(this).fadeIn(speed);
                    });
                })(id, $target);
            }
        }

        // start next batch once this response is processed
        retrieveNextIcons();
    }
})(jQuery);