/*
 * jQuery UI Accordion 1.8.3
 *
 * Copyright 2010, AUTHORS.txt (http://jqueryui.com/about)
 * Dual licensed under the MIT or GPL Version 2 licenses.
 * http://jquery.org/license
 *
 * http://docs.jquery.com/UI/Accordion
 *
 * Depends:
 *	jquery.ui.core.js
 *	jquery.ui.widget.js
 */
(function( $, undefined ) {

    $.widget( "ui.accordion", {
        options: {
            active: 0,
            animated: "slide",
            autoHeight: true,
            clearStyle: false,
            collapsible: false,
            event: "click",
            fillSpace: false,
            header: "> li > :first-child,> :not(li):even",
            icons: {
                header: "ui-icon-triangle-1-e",
                headerSelected: "ui-icon-triangle-1-s"
            },
            navigation: false,
            navigationFilter: function() {
                return this.href.toLowerCase() === location.href.toLowerCase();
            }
        },

        _create: function() {
            var self = this,
                options = self.options;

            self.running = 0;

            self.element
                .addClass( "ui-accordion ui-widget ui-helper-reset" )
                // in lack of child-selectors in CSS
                // we need to mark top-LIs in a UL-accordion for some IE-fix
                .children( "li" )
                .addClass( "ui-accordion-li-fix" );

            self.headers = self.element.find( options.header )
                .addClass( "ui-accordion-header ui-helper-reset ui-state-default ui-corner-all" )
                .bind( "mouseenter.accordion", function() {
                    if ( options.disabled ) {
                        return;
                    }
                    $( this ).addClass( "ui-state-hover" );
                })
                .bind( "mouseleave.accordion", function() {
                    if ( options.disabled ) {
                        return;
                    }
                    $( this ).removeClass( "ui-state-hover" );
                })
                .bind( "focus.accordion", function() {
                    if ( options.disabled ) {
                        return;
                    }
                    $( this ).addClass( "ui-state-focus" );
                })
                .bind( "blur.accordion", function() {
                    if ( options.disabled ) {
                        return;
                    }
                    $( this ).removeClass( "ui-state-focus" );
                });

            self.headers.next()
                .addClass( "ui-accordion-content ui-helper-reset ui-widget-content ui-corner-bottom" );

            if ( options.navigation ) {
                var current = self.element.find( "a" ).filter( options.navigationFilter ).eq( 0 );
                if ( current.length ) {
                    var header = current.closest( ".ui-accordion-header" );
                    if ( header.length ) {
                        // anchor within header
                        self.active = header;
                    } else {
                        // anchor within content
                        self.active = current.closest( ".ui-accordion-content" ).prev();
                    }
                }
            }

            self.active = self._findActive( self.active || options.active )
                .addClass( "ui-state-default ui-state-active" )
                .toggleClass( "ui-corner-all ui-corner-top" );
            self.active.next().addClass( "ui-accordion-content-active" );

            self._createIcons();
            self.resize();

            // ARIA
            self.element.attr( "role", "tablist" );

            self.headers
                .attr( "role", "tab" )
                .bind( "keydown.accordion", function( event ) {
                    return self._keydown( event );
                })
                .next()
                .attr( "role", "tabpanel" );

            self.headers
                .not( self.active || "" )
                .attr({
                    "aria-expanded": "false",
                    tabIndex: -1
                })
                .next()
                .hide();

            // make sure at least one header is in the tab order
            if ( !self.active.length ) {
                self.headers.eq( 0 ).attr( "tabIndex", 0 );
            } else {
                self.active
                    .attr({
                        "aria-expanded": "true",
                        tabIndex: 0
                    });
            }

            // only need links in tab order for Safari
            if ( !$.browser.safari ) {
                self.headers.find( "a" ).attr( "tabIndex", -1 );
            }

            if ( options.event ) {
                self.headers.bind( options.event.split(" ").join(".accordion ") + ".accordion", function(event) {
                    self._clickHandler.call( self, event, this );
                    event.preventDefault();
                });
            }
        },

        _createIcons: function() {
            var options = this.options;
            if ( options.icons ) {
                $( "<span></span>" )
                    .addClass( "ui-icon " + options.icons.header )
                    .prependTo( this.headers );
                this.active.children( ".ui-icon" )
                    .toggleClass(options.icons.header)
                    .toggleClass(options.icons.headerSelected);
                this.element.addClass( "ui-accordion-icons" );
            }
        },

        _destroyIcons: function() {
            this.headers.children( ".ui-icon" ).remove();
            this.element.removeClass( "ui-accordion-icons" );
        },

        destroy: function() {
            var options = this.options;

            this.element
                .removeClass( "ui-accordion ui-widget ui-helper-reset" )
                .removeAttr( "role" );

            this.headers
                .unbind( ".accordion" )
                .removeClass( "ui-accordion-header ui-accordion-disabled ui-helper-reset ui-state-default ui-corner-all ui-state-active ui-state-disabled ui-corner-top" )
                .removeAttr( "role" )
                .removeAttr( "aria-expanded" )
                .removeAttr( "tabIndex" );

            this.headers.find( "a" ).removeAttr( "tabIndex" );
            this._destroyIcons();
            var contents = this.headers.next()
                .css( "display", "" )
                .removeAttr( "role" )
                .removeClass( "ui-helper-reset ui-widget-content ui-corner-bottom ui-accordion-content ui-accordion-content-active ui-accordion-disabled ui-state-disabled" );
            if ( options.autoHeight || options.fillHeight ) {
                contents.css( "height", "" );
            }

            return $.Widget.prototype.destroy.call( this );
        },

        _setOption: function( key, value ) {
            $.Widget.prototype._setOption.apply( this, arguments );

            if ( key == "active" ) {
                this.activate( value );
            }
            if ( key == "icons" ) {
                this._destroyIcons();
                if ( value ) {
                    this._createIcons();
                }
            }
            // #5332 - opacity doesn't cascade to positioned elements in IE
            // so we need to add the disabled class to the headers and panels
            if ( key == "disabled" ) {
                this.headers.add(this.headers.next())
                    [ value ? "addClass" : "removeClass" ](
                        "ui-accordion-disabled ui-state-disabled" );
            }
        },

        _keydown: function( event ) {
            if ( this.options.disabled || event.altKey || event.ctrlKey ) {
                return;
            }

            var keyCode = $.ui.keyCode,
                length = this.headers.length,
                currentIndex = this.headers.index( event.target ),
                toFocus = false;

            switch ( event.keyCode ) {
                case keyCode.RIGHT:
                case keyCode.DOWN:
                    toFocus = this.headers[ ( currentIndex + 1 ) % length ];
                    break;
                case keyCode.LEFT:
                case keyCode.UP:
                    toFocus = this.headers[ ( currentIndex - 1 + length ) % length ];
                    break;
                case keyCode.SPACE:
                case keyCode.ENTER:
                    this._clickHandler( { target: event.target }, event.target );
                    event.preventDefault();
            }

            if ( toFocus ) {
                $( event.target ).attr( "tabIndex", -1 );
                $( toFocus ).attr( "tabIndex", 0 );
                toFocus.focus();
                return false;
            }

            return true;
        },

        resize: function() {
            var options = this.options,
                maxHeight;

            if ( options.fillSpace ) {
                if ( $.browser.msie ) {
                    var defOverflow = this.element.parent().css( "overflow" );
                    this.element.parent().css( "overflow", "hidden");
                }
                maxHeight = this.element.parent().height();
                if ($.browser.msie) {
                    this.element.parent().css( "overflow", defOverflow );
                }

                this.headers.each(function() {
                    maxHeight -= $( this ).outerHeight( true );
                });

                this.headers.next()
                    .each(function() {
                        $( this ).height( Math.max( 0, maxHeight -
                            $( this ).innerHeight() + $( this ).height() ) );
                    })
                    .css( "overflow", "auto" );
            } else if ( options.autoHeight ) {
                maxHeight = 0;
                this.headers.next()
                    .each(function() {
                        maxHeight = Math.max( maxHeight, $( this ).height( "" ).height() );
                    })
                    .height( maxHeight );
            }

            return this;
        },

        activate: function( index ) {
            // TODO this gets called on init, changing the option without an explicit call for that
            this.options.active = index;
            // call clickHandler with custom event
            var active = this._findActive( index )[ 0 ];
            this._clickHandler( { target: active }, active );

            return this;
        },

        _findActive: function( selector ) {
            return selector
                ? typeof selector === "number"
                ? this.headers.filter( ":eq(" + selector + ")" )
                : this.headers.not( this.headers.not( selector ) )
                : selector === false
                ? $( [] )
                : this.headers.filter( ":eq(0)" );
        },

        // TODO isn't event.target enough? why the separate target argument?
        _clickHandler: function( event, target ) {
            var options = this.options;
            if ( options.disabled ) {
                return;
            }

            // called only when using activate(false) to close all parts programmatically
            if ( !event.target ) {
                if ( !options.collapsible ) {
                    return;
                }
                this.active
                    .removeClass( "ui-state-active ui-corner-top" )
                    .addClass( "ui-state-default ui-corner-all" )
                    .children( ".ui-icon" )
                    .removeClass( options.icons.headerSelected )
                    .addClass( options.icons.header );
                this.active.next().addClass( "ui-accordion-content-active" );
                var toHide = this.active.next(),
                    data = {
                        options: options,
                        newHeader: $( [] ),
                        oldHeader: options.active,
                        newContent: $( [] ),
                        oldContent: toHide
                    },
                    toShow = ( this.active = $( [] ) );
                this._toggle( toShow, toHide, data );
                return;
            }

            // get the click target
            var clicked = $( event.currentTarget || target ),
                clickedIsActive = clicked[0] === this.active[0];

            // TODO the option is changed, is that correct?
            // TODO if it is correct, shouldn't that happen after determining that the click is valid?
            options.active = options.collapsible && clickedIsActive ?
                false :
                this.headers.index( clicked );

            // if animations are still active, or the active header is the target, ignore click
            if ( this.running || ( !options.collapsible && clickedIsActive ) ) {
                return;
            }

            // switch classes
            this.active
                .removeClass( "ui-state-active ui-corner-top" )
                .addClass( "ui-state-default ui-corner-all" )
                .children( ".ui-icon" )
                .removeClass( options.icons.headerSelected )
                .addClass( options.icons.header );
            if ( !clickedIsActive ) {
                clicked
                    .removeClass( "ui-state-default ui-corner-all" )
                    .addClass( "ui-state-active ui-corner-top" )
                    .children( ".ui-icon" )
                    .removeClass( options.icons.header )
                    .addClass( options.icons.headerSelected );
                clicked
                    .next()
                    .addClass( "ui-accordion-content-active" );
            }

            // find elements to show and hide
            var toShow = clicked.next(),
                toHide = this.active.next(),
                data = {
                    options: options,
                    newHeader: clickedIsActive && options.collapsible ? $([]) : clicked,
                    oldHeader: this.active,
                    newContent: clickedIsActive && options.collapsible ? $([]) : toShow,
                    oldContent: toHide
                },
                down = this.headers.index( this.active[0] ) > this.headers.index( clicked[0] );

            this.active = clickedIsActive ? $([]) : clicked;
            this._toggle( toShow, toHide, data, clickedIsActive, down );

            return;
        },

        _toggle: function( toShow, toHide, data, clickedIsActive, down ) {
            var self = this,
                options = self.options;

            self.toShow = toShow;
            self.toHide = toHide;
            self.data = data;

            var complete = function() {
                if ( !self ) {
                    return;
                }
                return self._completed.apply( self, arguments );
            };

            // trigger changestart event
            self._trigger( "changestart", null, self.data );

            // count elements to animate
            self.running = toHide.size() === 0 ? toShow.size() : toHide.size();

            if ( options.animated ) {
                var animOptions = {};

                if ( options.collapsible && clickedIsActive ) {
                    animOptions = {
                        toShow: $( [] ),
                        toHide: toHide,
                        complete: complete,
                        down: down,
                        autoHeight: options.autoHeight || options.fillSpace
                    };
                } else {
                    animOptions = {
                        toShow: toShow,
                        toHide: toHide,
                        complete: complete,
                        down: down,
                        autoHeight: options.autoHeight || options.fillSpace
                    };
                }

                if ( !options.proxied ) {
                    options.proxied = options.animated;
                }

                if ( !options.proxiedDuration ) {
                    options.proxiedDuration = options.duration;
                }

                options.animated = $.isFunction( options.proxied ) ?
                    options.proxied( animOptions ) :
                    options.proxied;

                options.duration = $.isFunction( options.proxiedDuration ) ?
                    options.proxiedDuration( animOptions ) :
                    options.proxiedDuration;

                var animations = $.ui.accordion.animations,
                    duration = options.duration,
                    easing = options.animated;

                if ( easing && !animations[ easing ] && !$.easing[ easing ] ) {
                    easing = "slide";
                }
                if ( !animations[ easing ] ) {
                    animations[ easing ] = function( options ) {
                        this.slide( options, {
                            easing: easing,
                            duration: duration || 700
                        });
                    };
                }

                animations[ easing ]( animOptions );
            } else {
                if ( options.collapsible && clickedIsActive ) {
                    toShow.toggle();
                } else {
                    toHide.hide();
                    toShow.show();
                }

                complete( true );
            }

            // TODO assert that the blur and focus triggers are really necessary, remove otherwise
            toHide.prev()
                .attr({
                    "aria-expanded": "false",
                    tabIndex: -1
                })
                .blur();
            toShow.prev()
                .attr({
                    "aria-expanded": "true",
                    tabIndex: 0
                })
                .focus();
        },

        _completed: function( cancel ) {
            this.running = cancel ? 0 : --this.running;
            if ( this.running ) {
                return;
            }

            if ( this.options.clearStyle ) {
                this.toShow.add( this.toHide ).css({
                    height: "",
                    overflow: ""
                });
            }

            // other classes are removed before the animation; this one needs to stay until completed
            this.toHide.removeClass( "ui-accordion-content-active" );

            this._trigger( "change", null, this.data );
        }
    });

    $.extend( $.ui.accordion, {
        version: "1.8.3",
        animations: {
            slide: function( options, additions ) {
                options = $.extend({
                    easing: "swing",
                    duration: 300
                }, options, additions );
                if ( !options.toHide.size() ) {
                    options.toShow.animate({
                        height: "show",
                        paddingTop: "show",
                        paddingBottom: "show"
                    }, options );
                    return;
                }
                if ( !options.toShow.size() ) {
                    options.toHide.animate({
                        height: "hide",
                        paddingTop: "hide",
                        paddingBottom: "hide"
                    }, options );
                    return;
                }
                var overflow = options.toShow.css( "overflow" ),
                    percentDone = 0,
                    showProps = {},
                    hideProps = {},
                    fxAttrs = [ "height", "paddingTop", "paddingBottom" ],
                    originalWidth;
                // fix width before calculating height of hidden element
                var s = options.toShow;
                originalWidth = s[0].style.width;
                s.width( parseInt( s.parent().width(), 10 )
                    - parseInt( s.css( "paddingLeft" ), 10 )
                    - parseInt( s.css( "paddingRight" ), 10 )
                    - ( parseInt( s.css( "borderLeftWidth" ), 10 ) || 0 )
                    - ( parseInt( s.css( "borderRightWidth" ), 10) || 0 ) );

                $.each( fxAttrs, function( i, prop ) {
                    hideProps[ prop ] = "hide";

                    var parts = ( "" + $.css( options.toShow[0], prop ) ).match( /^([\d+-.]+)(.*)$/ );
                    showProps[ prop ] = {
                        value: parts[ 1 ],
                        unit: parts[ 2 ] || "px"
                    };
                });
                options.toShow.css({ height: 0, overflow: "hidden" }).show();
                options.toHide
                    .filter( ":hidden" )
                    .each( options.complete )
                    .end()
                    .filter( ":visible" )
                    .animate( hideProps, {
                        step: function( now, settings ) {
                            // only calculate the percent when animating height
                            // IE gets very inconsistent results when animating elements
                            // with small values, which is common for padding
                            if ( settings.prop == "height" ) {
                                percentDone = ( settings.end - settings.start === 0 ) ? 0 :
                                    ( settings.now - settings.start ) / ( settings.end - settings.start );
                            }

                            options.toShow[ 0 ].style[ settings.prop ] =
                                ( percentDone * showProps[ settings.prop ].value )
                                    + showProps[ settings.prop ].unit;
                        },
                        duration: options.duration,
                        easing: options.easing,
                        complete: function() {
                            if ( !options.autoHeight ) {
                                options.toShow.css( "height", "" );
                            }
                            options.toShow.css({
                                width: originalWidth,
                                overflow: overflow
                            });
                            options.complete();
                        }
                    });
            },
            bounceslide: function( options ) {
                this.slide( options, {
                    easing: options.down ? "easeOutBounce" : "swing",
                    duration: options.down ? 1000 : 200
                });
            }
        }
    });

})( jQuery );