').addClass('end'))
                .children(), {
                    panel: $panel
                }
            );
        },
        /**
         * Get breadcrumbs matching specified panels
         */
        filter: function($panels) {
            var $breadcrumbs = $('#breadcrumbs ul li');
            var $result = $([]);
            $panels.each(function() {
                var $panel = $(this);
                $.merge(
                    $result,
                    $.merge(
                        $breadcrumbs.filter(function() {
                            return $(this).index('#breadcrumbs ul li') == $panel.index();
                        }),
                        // Also include ends
                        $breadcrumbs.siblings('div.end').filter(function() {
                            return $(this).index('div.end') == $panel.index() + 1;
                        })
                    )
                );
            });
            return $result;
        }
    };
    /**
     * Container-related functions
     */
    var _container = cloudStack.ui.widgets.browser.container = {
        /**
         * Get all panels from container
         */
        panels: function($container) {
            return $container.find('div.panel');
        }
    };
    /**
     * Panel-related functions
     */
    var _panel = cloudStack.ui.widgets.browser.panel = {
        /**
         * Compute width of panel, relative to container
         */
        width: function($container, options) {
            options = options ? options : {};
            var width = $container.find('div.panel').size() < 1 || !options.partial ?
                $container.width() : $container.width() - $container.width() / 4;
            return width;
        },
        /**
         * Get left position
         */
        position: function($container, options) {
            return $container.find('div.panel').size() <= 1 || !options.partial ?
                0 : _panel.width($container, options) - _panel.width($container, options) / 1.5;
        },
        /**
         * Get the top panel z-index, for proper stacking
         */
        topIndex: function($container) {
            var base = 50; // Minimum z-index
            return Math.max.apply(
                null,
                $.map(
                    $container.find('div.panel'),
                    function(elem) {
                        return parseInt($(elem).css('z-index')) || base;
                    }
                )
            ) + 1;
        },
        /**
         * State when panel is outside container
         */
        initialState: function($container) {
            return {
                left: $container.width()
            };
        },
        /**
         * Get panel and breadcrumb behind specific panel
         */
        lower: function($container, $panel) {
            return _container.panels($container).filter(function() {
                return $(this).index() < $panel.index();
            });
        },
        /**
         * Get panel and breadcrumb stacked above specific panel
         */
        higher: function($container, $panel) {
            return _container.panels($container).filter(function() {
                return $(this).index() > $panel.index();
            });
        },
        /**
         * Generate new panel
         */
        create: function($container, options) {
            var $panel = $('
').addClass('panel').css({
                position: 'absolute',
                width: _panel.width($container, {
                    partial: options.partial
                }),
                zIndex: _panel.topIndex($container)
            }).append(
                // Shadow
                $('
').addClass('shadow')
            ).append(options.data);
            return $panel;
        }
    };
    /**
     * Browser -- jQuery widget
     */
    $.widget('cloudStack.cloudBrowser', {
        _init: function() {
            this.element.addClass('cloudStack-widget cloudBrowser');
            $('#breadcrumbs').append(
                $('
')
            );
        },
        /**
         * Make target panel the top-most
         */
        selectPanel: function(args) {
            var $panel = args.panel;
            var $container = this.element;
            var $toShow = _panel.lower($container, $panel);
            var $toRemove = _panel.higher($container, $panel);
            var complete = args.complete;
            _breadcrumb.filter($toRemove).remove();
            _breadcrumb.filter($panel.siblings()).removeClass('active');
            _breadcrumb.filter($panel).addClass('active');
            _breadcrumb.filter($('div.panel')).find('span').css({
                opacity: 1
            });
            _breadcrumb.filter(
                $('div.panel.maximized')
                .removeClass('maximized')
                .addClass('reduced')
            ).removeClass('active maximized');
            $toRemove.remove();
            $toShow.show();
            $panel.css({
                left: _panel.position($container, {
                    maximized: $panel.hasClass('always-maximized')
                })
            });
            $panel.show().removeClass('reduced');
        },
        /**
         * Toggle selected panel as fully expanded, hiding/showing other panels
         */
        toggleMaximizePanel: function(args) {
            var $panel = args.panel;
            var $container = this.element;
            var $toHide = $panel.siblings(':not(.always-maximized)');
            var $shadow = $toHide.find('div.shadow');
            if (args.panel.hasClass('maximized')) {
                _breadcrumb.filter($panel).removeClass('maximized');
                $panel.removeClass('maximized');
                $panel.addClass('reduced');
                _breadcrumb.filter($panel.siblings()).find('span').css({
                    opacity: 1
                });
                $toHide.css({
                    left: _panel.position($container, {})
                });
                $shadow.show();
            } else {
                _breadcrumb.filter($panel).addClass('maximized');
                $panel.removeClass('reduced');
                $panel.addClass('maximized');
                $toHide.css(_panel.initialState($container));
                $shadow.hide();
            }
        },
        /**
         * Append new panel to end of container
         */
        addPanel: function(args) {
            var duration = args.duration ? args.duration : 500;
            var $container = this.element;
            var $parent = args.parent;
            var $panel, $reduced, targetPosition;
            // Create panel
            $panel = _panel.create(this.element, {
                partial: args.partial,
                data: args.data
            });
            // Remove existing panels from parent
            if ($parent) {
                // Cleanup transitioning panels -- prevent old complete actions from running
                $parent.siblings().stop();
                _breadcrumb.filter(
                    $('div.panel.maximized')
                    .removeClass('maximized')
                    .addClass('reduced')
                ).removeClass('active maximized');
                $parent.removeClass('maximized');
                _breadcrumb.filter($parent.next()).remove();
                $container.find($parent.next()).remove();
            }
            // Append panel
            $panel.appendTo($container);
            _breadcrumb.filter($panel.siblings()).removeClass('active');
            _breadcrumb.create($panel, args.title)
                .addClass('active')
                .appendTo('#breadcrumbs ul');
            // Reduced appearance for previous panels
            $panel.siblings().filter(function() {
                return $(this).index() < $panel.index();
            }).addClass('reduced');
            // Panel initial state
            if ($panel.index() == 0) $panel.addClass('always-maximized');
            $panel.css(
                _panel.initialState($container, $panel)
            );
            // Panel slide-in
            targetPosition = _panel.position($container, {
                maximized: args.maximizeIfSelected,
                partial: args.partial
            });
            if (!$panel.index()) {
                // Just show immediately if this is the first panel
                $panel.css({
                    left: targetPosition
                });
                if (args.complete) args.complete($panel, _breadcrumb.filter($panel));
            } else {
                // Animate slide-in
                $panel.css({
                    left: targetPosition
                });
                // Hide panels
                $panel.siblings().filter(function() {
                    return $(this).width() == $panel.width();
                });
                if ($panel.is(':visible') && args.complete) args.complete($panel);
            };
            return $panel;
        },
        /**
         * Clear all panels
         */
        removeAllPanels: function(args) {
            $('div.panel').stop(); // Prevent destroyed panels from animating
            this.element.find('div.panel').remove();
            $('#breadcrumbs').find('ul li').remove();
            $('#breadcrumbs').find('ul div.end').remove();
        }
    });
    $('#breadcrumbs li').live('click', cloudStack.ui.event.bind(
        'cloudBrowser', {
            'breadcrumb': function($target, $browser, data) {
                if ($('#browser').hasClass('panel-highlight')) {
                    return false;
                }
                $browser.cloudBrowser('selectPanel', {
                    panel: data.panel
                });
            }
        }
    ));
})(jQuery, cloudStack);