(function($) {

/**
 * Generate an indented list of links from a nav. Meant for use with panel().
 * @return {jQuery} jQuery object.
 */
$.fn.navList = function() {
        var     $this = $(this);
                $a = $this.find('a'),
                b = [];
        $a.each(function() {
                var     $this = $(this),
                        indent = Math.max(0, $this.parents('li').length - 1),
                        href = $this.attr('href'),
                        target = $this.attr('target');
                b.push(
                        '<a ' +
                                'class="link depth-' + indent + '"' +
                                ( (typeof target !== 'undefined' && target != '') ? ' target="' + target + '"' : '') +
                                ( (typeof href !== 'undefined' && href != '') ? ' href="' + href + '"' : '') +
                        '>' +
                                '<span class="indent-' + indent + '"></span>' +
                                $this.text() +
                        '</a>'
                );
        });
        return b.join('');
};
/**
 * Panel-ify an element.
 * @param {object} userConfig User config.
 * @return {jQuery} jQuery object.
 */
$.fn.panel = function(userConfig) {
        // No elements?
                if (this.length == 0)
                        return $this;
        // Multiple elements?
                if (this.length > 1) {
                        for (var i=0; i < this.length; i++)
                                $(this[i]).panel(userConfig);
                        return $this;
                }
        // Vars.
                var     $this = $(this),
                        $body = $('body'),
                        $window = $(window),
                        id = $this.attr('id'),
                        config;
        // Config.
                config = $.extend({
                        // Delay.
                                delay: 0,
                        // Hide panel on link click.
                                hideOnClick: false,
                        // Hide panel on escape keypress.
                                hideOnEscape: false,
                        // Hide panel on swipe.
                                hideOnSwipe: false,
                        // Reset scroll position on hide.
                                resetScroll: false,
                        // Reset forms on hide.
                                resetForms: false,
                        // Side of viewport the panel will appear.
                                side: null,
                        // Target element for "class".
                                target: $this,
                        // Class to toggle.
                                visibleClass: 'visible'
                }, userConfig);
                // Expand "target" if it's not a jQuery object already.
                        if (typeof config.target != 'jQuery')
                                config.target = $(config.target);
        // Panel.
                // Methods.
                        $this._hide = function(event) {
                                // Already hidden? Bail.
                                        if (!config.target.hasClass(config.visibleClass))
                                                return;
                                // If an event was provided, cancel it.
                                        if (event) {
                                                event.preventDefault();
                                                event.stopPropagation();
                                        }
                                // Hide.
                                        config.target.removeClass(config.visibleClass);
                                // Post-hide stuff.
                                        window.setTimeout(function() {
                                                // Reset scroll position.
                                                        if (config.resetScroll)
                                                                $this.scrollTop(0);
                                                // Reset forms.
                                                        if (config.resetForms)
                                                                $this.find('form').each(function() {
                                                                        this.reset();
                                                                });
                                        }, config.delay);
                        };
                // Vendor fixes.
                        $this
                                .css('-ms-overflow-style', '-ms-autohiding-scrollbar')
                                .css('-webkit-overflow-scrolling', 'touch');
                // Hide on click.
                        if (config.hideOnClick) {
                                $this.find('a')
                                        .css('-webkit-tap-highlight-color', 'rgba(0,0,0,0)');
                                $this
                                        .on('click', 'a', function(event) {
                                                var $a = $(this),
                                                        href = $a.attr('href'),
                                                        target = $a.attr('target');
                                                if (!href || href == '#' || href == '' || href == '#' + id)
                                                        return;
                                                // Cancel original event.
                                                        event.preventDefault();
                                                        event.stopPropagation();
                                                // Hide panel.
                                                        $this._hide();
                                                // Redirect to href.
                                                        window.setTimeout(function() {
                                                                if (target == '_blank')
                                                                        window.open(href);
                                                                else
                                                                        window.location.href = href;
                                                        }, config.delay + 10);
                                        });
                        }
                // Event: Touch stuff.
                        $this.on('touchstart', function(event) {
                                $this.touchPosX = event.originalEvent.touches[0].pageX;
                                $this.touchPosY = event.originalEvent.touches[0].pageY;
                        })
                        $this.on('touchmove', function(event) {
                                if ($this.touchPosX === null
                                ||      $this.touchPosY === null)
                                        return;
                                var     diffX = $this.touchPosX - event.originalEvent.touches[0].pageX,
                                        diffY = $this.touchPosY - event.originalEvent.touches[0].pageY,
                                        th = $this.outerHeight(),
                                        ts = ($this.get(0).scrollHeight - $this.scrollTop());
                                // Hide on swipe?
                                        if (config.hideOnSwipe) {
                                                var result = false,
                                                        boundary = 20,
                                                        delta = 50;
                                                switch (config.side) {
                                                        case 'left':
                                                                result = (diffY < boundary && diffY > (-1 * boundary)) && (diffX > delta);
                                                                break;
                                                        case 'right':
                                                                result = (diffY < boundary && diffY > (-1 * boundary)) && (diffX < (-1 * delta));
                                                                break;
                                                        case 'top':
                                                                result = (diffX < boundary && diffX > (-1 * boundary)) && (diffY > delta);
                                                                break;
                                                        case 'bottom':
                                                                result = (diffX < boundary && diffX > (-1 * boundary)) && (diffY < (-1 * delta));
                                                                break;
                                                        default:
                                                                break;
                                                }
                                                if (result) {
                                                        $this.touchPosX = null;
                                                        $this.touchPosY = null;
                                                        $this._hide();
                                                        return false;
                                                }
                                        }
                                // Prevent vertical scrolling past the top or bottom.
                                        if (($this.scrollTop() < 0 && diffY < 0)
                                        || (ts > (th - 2) && ts < (th + 2) && diffY > 0)) {
                                                event.preventDefault();
                                                event.stopPropagation();
                                        }
                        });
                // Event: Prevent certain events inside the panel from bubbling.
                        $this.on('click touchend touchstart touchmove', function(event) {
                                event.stopPropagation();
                        });
                // Event: Hide panel if a child anchor tag pointing to its ID is clicked.
                        $this.on('click', 'a[href="#' + id + '"]', function(event) {
                                event.preventDefault();
                                event.stopPropagation();
                                config.target.removeClass(config.visibleClass);
                        });
        // Body.
                // Event: Hide panel on body click/tap.
                        $body.on('click touchend', function(event) {
                                $this._hide(event);
                        });
                // Event: Toggle.
                        $body.on('click', 'a[href="#' + id + '"]', function(event) {
                                event.preventDefault();
                                event.stopPropagation();
                                config.target.toggleClass(config.visibleClass);
                        });
        // Window.
                // Event: Hide on ESC.
                        if (config.hideOnEscape)
                                $window.on('keydown', function(event) {
                                        if (event.keyCode == 27)
                                                $this._hide(event);
                                });
        return $this;
};
/**
 * Apply "placeholder" attribute polyfill to one or more forms.
 * @return {jQuery} jQuery object.
 */
$.fn.placeholder = function() {
        // Browser natively supports placeholders? Bail.
                if (typeof (document.createElement('input')).placeholder != 'undefined')
                        return $(this);
        // No elements?
                if (this.length == 0)
                        return $this;
        // Multiple elements?
                if (this.length > 1) {
                        for (var i=0; i < this.length; i++)
                                $(this[i]).placeholder();
                        return $this;
                }
        // Vars.
                var $this = $(this);
        // Text, TextArea.
                $this.find('input[type=text],textarea')
                        .each(function() {
                                var i = $(this);
                                if (i.val() == ''
                                ||  i.val() == i.attr('placeholder'))
                                        i
                                                .addClass('polyfill-placeholder')
                                                .val(i.attr('placeholder'));
                        })
                        .on('blur', function() {
                                var i = $(this);
                                if (i.attr('name').match(/-polyfill-field$/))
                                        return;
                                if (i.val() == '')
                                        i
                                                .addClass('polyfill-placeholder')
                                                .val(i.attr('placeholder'));
                        })
                        .on('focus', function() {
                                var i = $(this);
                                if (i.attr('name').match(/-polyfill-field$/))
                                        return;
                                if (i.val() == i.attr('placeholder'))
                                        i
                                                .removeClass('polyfill-placeholder')
                                                .val('');
                        });
        // Password.
                $this.find('input[type=password]')
                        .each(function() {
                                var i = $(this);
                                var x = $(
                                                        $('<div>')
                                                                .append(i.clone())
                                                                .remove()
                                                                .html()
                                                                .replace(/type="password"/i, 'type="text"')
                                                                .replace(/type=password/i, 'type=text')
                                );
                                if (i.attr('id') != '')
                                        x.attr('id', i.attr('id') + '-polyfill-field');
                                if (i.attr('name') != '')
                                        x.attr('name', i.attr('name') + '-polyfill-field');
                                x.addClass('polyfill-placeholder')
                                        .val(x.attr('placeholder')).insertAfter(i);
                                if (i.val() == '')
                                        i.hide();
                                else
                                        x.hide();
                                i
                                        .on('blur', function(event) {
                                                event.preventDefault();
                                                var x = i.parent().find('input[name=' + i.attr('name') + '-polyfill-field]');
                                                if (i.val() == '') {
                                                        i.hide();
                                                        x.show();
                                                }
                                        });
                                x
                                        .on('focus', function(event) {
                                                event.preventDefault();
                                                var i = x.parent().find('input[name=' + x.attr('name').replace('-polyfill-field', '') + ']');
                                                x.hide();
                                                i
                                                        .show()
                                                        .focus();
                                        })
                                        .on('keypress', function(event) {
                                                event.preventDefault();
                                                x.val('');
                                        });
                        });
        // Events.
                $this
                        .on('submit', function() {
                                $this.find('input[type=text],input[type=password],textarea')
                                        .each(function(event) {
                                                var i = $(this);
                                                if (i.attr('name').match(/-polyfill-field$/))
                                                        i.attr('name', '');
                                                if (i.val() == i.attr('placeholder')) {
                                                        i.removeClass('polyfill-placeholder');
                                                        i.val('');
                                                }
                                        });
                        })
                        .on('reset', function(event) {
                                event.preventDefault();
                                $this.find('select')
                                        .val($('option:first').val());
                                $this.find('input,textarea')
                                        .each(function() {
                                                var i = $(this),
                                                        x;
                                                i.removeClass('polyfill-placeholder');
                                                switch (this.type) {
                                                        case 'submit':
                                                        case 'reset':
                                                                break;
                                                        case 'password':
                                                                i.val(i.attr('defaultValue'));
                                                                x = i.parent().find('input[name=' + i.attr('name') + '-polyfill-field]');
                                                                if (i.val() == '') {
                                                                        i.hide();
                                                                        x.show();
                                                                }
                                                                else {
                                                                        i.show();
                                                                        x.hide();
                                                                }
                                                                break;
                                                        case 'checkbox':
                                                        case 'radio':
                                                                i.attr('checked', i.attr('defaultValue'));
                                                                break;
                                                        case 'text':
                                                        case 'textarea':
                                                                i.val(i.attr('defaultValue'));
                                                                if (i.val() == '') {
                                                                        i.addClass('polyfill-placeholder');
                                                                        i.val(i.attr('placeholder'));
                                                                }
                                                                break;
                                                        default:
                                                                i.val(i.attr('defaultValue'));
                                                                break;
                                                }
                                        });
                        });
        return $this;
};
/**
 * Moves elements to/from the first positions of their respective parents.
 * @param {jQuery} $elements Elements (or selector) to move.
 * @param {bool} condition If true, moves elements to the top. Otherwise, moves elements back to their original locations.
 */
$.prioritize = function($elements, condition) {
        var key = '__prioritize';
        // Expand $elements if it's not already a jQuery object.
                if (typeof $elements != 'jQuery')
                        $elements = $($elements);
        // Step through elements.
                $elements.each(function() {
                        var     $e = $(this), $p,
                                $parent = $e.parent();
                        // No parent? Bail.
                                if ($parent.length == 0)
                                        return;
                        // Not moved? Move it.
                                if (!$e.data(key)) {
                                        // Condition is false? Bail.
                                                if (!condition)
                                                        return;
                                        // Get placeholder (which will serve as our point of reference for when this element needs to move back).
                                                $p = $e.prev();
                                                // Couldn't find anything? Means this element's already at the top, so bail.
                                                        if ($p.length == 0)
                                                                return;
                                        // Move element to top of parent.
                                                $e.prependTo($parent);
                                        // Mark element as moved.
                                                $e.data(key, $p);
                                }
                        // Moved already?
                                else {
                                        // Condition is true? Bail.
                                                if (condition)
                                                        return;
                                        $p = $e.data(key);
                                        // Move element back to its original location (using our placeholder).
                                                $e.insertAfter($p);
                                        // Unmark element as moved.
                                                $e.removeData(key);
                                }
                });
};

})(jQuery);