/* jshint ignore:start */
(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);