
jQuery.fn.extend({
    // nextUntil is necessary, would be nice to have this in jQuery core
    nextUntil: function(expr) {
        var match = [];

        // We need to figure out which elements to push onto the array
        this.each(function() {
            // Traverse through the sibling nodes
            for (var i = this.nextSibling; i; i = i.nextSibling) {
                // Make sure that we're only dealing with elements
                if (i.nodeType != 1) continue;

                // If we find a match then we need to stop
                if (jQuery.filter(expr, [i]).r.length) break;

                // Otherwise, add it on to the stack
                match.push(i);
            }
        });

        return this.pushStack(match);
    },
    // the plugin method itself
    Accordion: function(settings) {
        // setup configuration
        var var1 = jQuery(':first-child', this);
        if (var1.html() != null) {
            settings = jQuery.extend({}, jQuery.Accordion.defaults, {
                // define context defaults
                header: jQuery(':first-child', this)[0].tagName // take first childs tagName as header
            }, settings);
        }

        // calculate active if not specified, using the first header
        var container = this,
			active = settings.active
				? jQuery(settings.active, this)
				: settings.active === false
					? jQuery("<div>")
					: jQuery(settings.header, this).eq(0),
			running = 0;

        container.find(settings.header)
			.not(active || "")
			.nextUntil(settings.header)
			.hide();
        active.addClass(settings.selectedClass);

        function clickHandler(event) {
            // get the click target
            var clicked = jQuery(event.target);

            // due to the event delegation model, we have to check if one
            // of the parent elements is our actual header, and find that
            if (clicked.parents(settings.header).length) {
                while (!clicked.is(settings.header)) {
                    clicked = clicked.parent();
                }
            }

            var clickedActive = clicked[0] == active[0];

            // if animations are still active, or the active header is the target, ignore click
            if (running || (settings.alwaysOpen && clickedActive) || !clicked.is(settings.header))
                return;

            // switch classes
            active.toggleClass(settings.selectedClass);
            if (!clickedActive) {
                clicked.addClass(settings.selectedClass);
            }

            // find elements to show and hide
            var toShow = clicked.nextUntil(settings.header),
				toHide = active.nextUntil(settings.header),
				data = [clicked, active, toShow, toHide];
            active = clickedActive ? jQuery([]) : clicked;
            // count elements to animate
            running = toHide.size() + toShow.size();
            var finished = function(cancel) {
                running = cancel ? 0 : --running;
                if (running)
                    return;

                // trigger custom change event
                container.trigger("change", data);
            };
            // TODO if hideSpeed is set to zero, animations are crappy
            // workaround: use hide instead
            // solution: animate should check for speed of 0 and do something about it
            if (settings.animated) {
                if (!settings.alwaysOpen && clickedActive) {
                    toShow.slideToggle(settings.showSpeed);
                    finished(true);
                } else {
                    toHide.filter(":hidden").each(finished).end().filter(":visible").slideUp(settings.hideSpeed, finished);
                    toShow.slideDown(settings.showSpeed, finished);
                }
            } else {
                if (!settings.alwaysOpen && clickedActive) {
                    toShow.toggle();
                } else {
                    toHide.hide();
                    toShow.show();
                }
                finished(true);
            }

            return false;
        };
        function activateHandlder(event, index) {
            // call clickHandler with custom event
            clickHandler({
                target: jQuery(settings.header, this)[index]
            });
        };

        return container
			.bind(settings.event, clickHandler)
			.bind("activate", activateHandlder);
    },
    // programmatic triggering
    activate: function(index) {
        return this.trigger('activate', [index || 0]);
    }
});

jQuery.Accordion = {};
jQuery.extend(jQuery.Accordion, {
	defaults: {
		selectedClass: "selected",
		showSpeed: 'slow',
		hideSpeed: 'fast',
		alwaysOpen: true,
		animated: true,
		event: "click"
	},
	setDefaults: function(settings) {
		jQuery.extend(jQuery.Accordion.defaults, settings);
	}
});

