/**
 * A multiple image selector plugin.
 * Currently, requires the query string object plugin:
 * http://plugins.jquery.com/project/query-object
 */
(function($) {

    /**
     * Constructor for multiple image selector.
     * Sets up on click events and slimbox display.
     */
    var MultipleImageSelector = function(source, options) {
        var self = this;
        var size_options = {
            main_image_size:            [300, 300],
            additional_image_size:      [100, 100],
            expanded_image_size:        [500, 500]
        };
        var this_window    = $(window);
        var max_image_size = Math.min(this_window.width(), this_window.height()) * 0.8;
        var max_sizes = {
            expanded_image_size:        [max_image_size, max_image_size]
        };
        var base_options = {
            main_image_container:       '.multiple_image-main_container',
            additional_image_container: '.multiple_images',
            additional_image_class:     'multiple_image-additional'
        };
        //
        // For each size:
        // replace with default values if they're not set.
        $.each(size_options, function(i,v) {
            if (i in options) {
                if (Number(options[i][0]) == 0 && Number(options[i][1]) == 0) {
                    options[i][0] = v[0];
                    options[i][1] = v[1];
                }
                options[i][0] = Number(options[i][0]);
                options[i][1] = Number(options[i][1]);
            }
        });
        $.each(max_sizes, function(i,v) {
            if (i in options) {
                options[i][0] = Math.min(options[i][0], v[0]);
                options[i][1] = Math.min(options[i][1], v[1]);
            }
        });

        options = self.options = $.extend(base_options, size_options, options);

        //
        // The index of the currently viewed image.
        self.image_index          = 0;
        self.expanded_image_index = 0;

        //
        // Callback function for vertical aligning an element.
        // Used as an onload callback for images.
        // Vertically aligning an image both immediately and when it's loaded
        // ensure that it is vertically aligned.
        var valign_this = function() { $(this).valign(); };

        //
        // Add a copy of the main image to the multiple image selector.
        self.main_image = $(options.main_image_container+' img:first', source);

        //
        // Adjust width/height and add event to each image container.
        var additional_image_containers = $(options.additional_image_container, source).children();
        additional_image_containers
            .each(function(index) { $(this).click(function() { self.setImage(index); return false; }); })
            .css('cursor', 'pointer');
        if (options.additional_image_size[0] > 0) {
            additional_image_containers.width(options.additional_image_size[0]);
        }
        if (options.additional_image_size[1] > 0) {
            additional_image_containers.height(options.additional_image_size[1]);
        }

        //
        // Ensure each image is vertically aligned, by aligning it now and also
        // when it loads.
        $('.'+options.additional_image_class, source).load(valign_this).each(valign_this);

        //
        // Get every additional image, and add an onclick event to it to display
        // that image in the main display. Also, add the image (full size) to
        // the slimbox.
        self.slimbox_images = [];
        self.additional_images = $('.'+options.additional_image_class, source);
        self.additional_images.each(function() {
            //
            // Add each of these images (at full-size) to the slimbox group.
            var image = $.replaceUrlParts($(this).attr('src'), {
                w:   options.expanded_image_size[0],
                h:   options.expanded_image_size[1],
                far: 'C',
                bg:  'FFFFFF'
            });
            self.slimbox_images.push(image);
        });

        //
        // Create a mini-selector to add to the slimbox, if it includes multiple
        // images.
        var slimbox_images = $('<ul class="multiple_images-slimbox" />');
        if (self.slimbox_images.length > 1) {
            $.each(self.slimbox_images, function(i,v) {
                var slimbox_image_container = $('<li />').attr({id: 'lbLink-'+String(i), title: 'Click to expand'}).css('cursor', 'pointer');
                slimbox_image_container.append($('<img>').attr('src', $.replaceUrlParts(v, {w: 0, h: 40})));
                slimbox_images.append(slimbox_image_container);
            });
        }

        //
        // Try adding slimbox selectors to all images.
        // May need to expose slimbox's changeImage method.
        $.each(self.slimbox_images, function(i,v) {
            self.slimbox_images[i] = [v, slimbox_images];
        });

        //
        // When the main image's container is clicked, the current image is
        // displayed in the slimbox.
        // Its dimensions are also updated.
        var main_image_container = $(options.main_image_container, source);
        main_image_container
            .prepend('<div class="overlay" style="display:none; z-index:10000; position:absolute; text-align:center;"><img src="/images/click_to_expand.png" /></div>')
            .removeAttr('title')
            .hover(function() {
                var overlay = $('.overlay', this);
                overlay.css({
                    marginTop:  (main_image_container.innerHeight()-overlay.outerHeight())/2,
                    marginLeft: (main_image_container.innerWidth() -overlay.outerWidth()) /2
                });
                overlay.stop().fadeTo('fast', 1);
            }, function() {
                $('.overlay', this).stop().fadeTo('fast', 0);
            })
            .click(function() {
                $('.overlay', this).stop().fadeTo('fast', 0);
                self.expandImage(); return false;
            })
            .css('cursor', 'pointer');
        if (options.main_image_size[0] > 0) {
            main_image_container.width(options.main_image_size[0]);
        }
        if (options.main_image_size[1] > 0) {
            main_image_container.height(options.main_image_size[1]);
        }
        self.main_image.load(valign_this).valign();
    };
    /**
     * Sets image for this multiple image selector.
     * @param int index
     *   The index of the image in the selector.
     */
    MultipleImageSelector.prototype.setImage = function(index) {
        var self = this;
        self.image_index = index;
        var image_source = $.replaceUrlPartsFrom(
            self.main_image.attr('src'),
            self.additional_images.eq(index).attr('src'),
            ['src', 'f']
        );
        self.main_image.attr('src', image_source);
    };
    /**
     * Retrieves index of currently displayed image.
     * @return int
     */
    MultipleImageSelector.prototype.getImage = function() {
        return this.image_index;
    };
    /**
     * Expands an image into a slimbox.
     * @param int index
     *   The index of the image to expand. If not set, then display the current
     *   image.
     */
    MultipleImageSelector.prototype.expandImage = function(index) {
        if (arguments.length > 0 && index != this.image_index) {
            this.setImage(index);
        }
        this.showExpandedImage(this.image_index);
    };
    /**
     * Shows an image in the expanded view, if it's open.
     * @param index
     * @return
     */
    MultipleImageSelector.prototype.showExpandedImage = function(index, is_instant) {
        this.expanded_image_index = index;
        var options = {
            loop: true,
            counterText: '',
            showCaptionImmediately: true
        };
        if (typeof is_instant == 'undefined' || !is_instant) {
            options = $.extend(options, {
                overlayFadeDuration: 100,
                resizeDuration: 100,
                captionAnimationDuration: 1
            });
        }
        else {
            options = $.extend(options, {
                overlayFadeDuration: 0,
                resizeDuration: 0,
                imageFadeDuration: 0,
                captionAnimationDuration: 1
            });
        }
        if (typeof $.slimbox == "function") {
            $.slimbox(this.slimbox_images, index, options);
        }
    };


    /**
     * Class for selecting DOM nodes that have a plugin attached, and for
     * running functions on the plugin objects.
     * @param string data_name
     *   The name of the data index which stores the plugin object.
     */
    var PluginSelector = function(data_name) {
        var self = this;
        self.data_name = data_name;
    };
    /**
     * Retrieves all DOM nodes in list which contain this plugin object.
     * @param jQuery list
     * @return jQuery
     */
    PluginSelector.prototype.getElements = function(list) {
        var data_name = this.data_name;
        return $($.grep(list, function(item) {
            return $(item).data(data_name);
        }));
    };
    /**
     * Runs a function on the first node in the list which contains this plugin
     * object, and returns the result of that function.
     * @param jQuery list
     * @param function fn
     *   The callback function to run. It takes the plugin object as its only
     *   parameter.
     * @param mixed default_return
     * @return mixed
     *   The result of fn() on the first plugin object, or default_return if
     *   there are none.
     */
    PluginSelector.prototype.runOnFirst = function(list, fn, default_return) {
        var data_name = this.data_name;
        var elements = this.getElements(list);
        if (elements.size()) {
            return fn(elements.eq(0).data(data_name));
        }
        else {
            return default_return;
        }
    };
    /**
     * Runs a function on all nodes in the list which contains this plugin
     * object.
     * @param jQuery list
     * @param function fn
     *   The callback function to run. It takes the plugin object as its only
     *   parameter.
     * @return array
     *   The result of fn() on all plugin objects.
     */
    PluginSelector.prototype.runOnAll = function(list, fn) {
        var data_name = this.data_name;
        var result = [];
        this.getElements(list).each(function() {
            result.push(fn($(this).data(data_name)));
        });
        return result;
    };




    /**
     * Retrieves a multiple image selector.
     */
    var getMultipleImageSelectors = function(list) {
        return $($.grep(list, function(item) {
            return $(item).data('multiple_image_selector');
        }));
    };

    var data_name = 'multiple_image_selector';

    /**
     * Creates a Carousel2D object on each matching object.
     * @param array options
     * @return jQuery
     *   A list of DOM elements that correspond to the new Carousel2D objects.
     */
    $.fn.multipleImageSelector = function(options) {
        var doms = [];
        $(this).each(function() {
            var multiple_image_selector = new MultipleImageSelector(this, options);
            $(this).data(data_name, multiple_image_selector);
        });
        return $(doms);
    };


    /**
     * Gets index of current image of first match if no index is passed, or sets
     * index for all matches if an index is passed.
     * @param int index
     *   The vertical index (starting from 0) to set.
     * @return int
     *   The index of the first multiple image selector.
     */
    $.fn.multipleImageSelector_image = function(index) {
        var plugin_selector = new PluginSelector(data_name);
        if (arguments.length == 0) {
            return plugin_selector.runOnFirst(this, function(c) { return c.getImage(); }, -1);
        }
        else {
            plugin_selector.runOnAll(this, function(c) { c.setImage(index); });
        }
    };


    /**
     * Expands an image on the first multiple image selector in this match.
     * @param int index
     *   If passed, the image at this index is shown - otherwise, the currently
     *   selected one is.
     */
    $.fn.multipleImageSelector_expand = function(index) {
        var plugin_selector = new PluginSelector(data_name);
        var callback = (arguments.length == 0) ?
            function(c) { c.expandImage(); } :
            function(c) { c.expandImage(index); } ;
        plugin_selector.runOnFirst(this, callback);
    };
})(jQuery);
