$.fn.extend({
	accordion: function (options) {
		var _options = $.extend({
			event: 'click',				//Event which triggers collapse/expand
			header: 'h2',				//Header css selector
			selectedClass: '',			//Selected class
			alwaysOpen: true,			//1 tab is always open 
			initialySelected: 0,		//Initialy opened tab index
			animation: '',				//Aimation - values are: slide, fade
			animationSpeed: 'fast',		//Animation speed (jQuery animation speed value)
			preserveHeight: false,		//Container height is always the maximum size
			callbackOpen: null,			//Callback for tab open event
			callbackClose: null			//Callback for tab close event
		}, options);
		
		_options.container = $(this[0]);
		_options.height_container = 0;
		_options.curOpen = null;
		_options.inProgress = false;
		_options.disabled = false;
		_options.siblings = [];			//List of other accordion nodes 

		/* If accordion was executed on more than 1 node
		   create for each node separate accordion object */
		if (this.length > 1)
		{
			for(var i=1; i<this.length; i++)
			{
				_options.siblings.push($(this[i]).accordion(options)); 
			}
		}
		
		_options.siblings = $(_options.siblings);
	
		/* Constructor */
		var _init = function () {
			var headers = $(_options.header, _options.container);
			var container_h = _options.container.innerHeight();
			var max_cont_h = 0;
			var tab_is_open = false;
			
			for(var i=0,j=headers.length; i<j; i++)
			{
				if (headers[i])
				{
					var header = $(headers[i]);
					var element = header.next();
					
					var tmp_h = element.outerHeight({margin: true});	//Node height
					container_h -= tmp_h;								//Container height without content
					max_cont_h = Math.max(max_cont_h, tmp_h);			//Maximum content node height
					
					if (_options.initialySelected == i)
					{
						/* Tab is opened */
						header.addClass(_options.selectedClass);
						element.addClass(_options.selectedClass).show();
						_options.curOpen = element;
						tab_is_open = true;
					}else{
						/* Tab is closed */
						header.removeClass(_options.selectedClass);
						element.removeClass(_options.selectedClass).hide();
					}
					
					/* Saving tab content height */
					element.get(0)._accordion_height = tmp_h;
					
					/* Attaching event */
					header.bind(_options.event, _open);
				}
			}
			
			/* Opening first tab if none is open and 1 must be open*/
			if (!tab_is_open && _options.alwaysOpen)
			{
				$(headers[0]).addClass(_options.selectedClass);
				_options.curOpen = $(headers[0]).next()
								   .addClass(_options.selectedClass)
								   .show();
			}
			
			/* Container height when all tabs are closed */
			_options.height_container = container_h;
			
			if (_options.preserveHeight) {
				/* Container has fixed height */
				_options.container.css({
					overflow: 'hidden',
					height: container_h + max_cont_h + 'px'
				});
			}else{
				/* Container has dinamic height */
				_options.container.css({ overflow: 'hidden' });
			}
		};
		
		/* Enable accordion */
		var _enable = function () {
			_options.disabled = false;
			_options.siblings.each(function () { this.enable(); });
		};
		
		/* Disable accordion */
		var _disable = function () {
			_options.disabled = true;
			_options.siblings.each(function () { this.disable(); });
		};
		
		/* Destroy accordion */
		var _destroy = function () {
			$(_options.header, _options.container).each(function () {
				$(this).unbind(_options.even, _open)
					   .next().get(0)._accordion_height = null;
			});
			
			_options.siblings.each(function () { this.destroy(); });
			for(var i=0; i< _options.siblings.length; i++)
			{
				_options.siblings[i] = null;
			}
			
			_options.container = null;
			_options.curOpen = null;
			_options.siblings = null;
			_enable = null;
			_disabeld = null;
			_destroy = null;
			_open = null;
			_close = null;
		};
		
		/* Open an accordion */
		var _open = function () {
			if (_options.inProgress || _options.disabled) return;
			
			var element = $(this).next();	//this references to heading, getting content node
			
			if (element.css('display') == 'none')
			{
				//Open new tab and close already opened
				
				if (!_options.preserveHeight)
				{
					/* Changing height manualy because content animations are not
					   synchronized, what causes containers height 'lag' */
					_options.container.animate({height: _options.height_container + element.get(0)._accordion_height + 'px'}, _options.animationSpeed);
				}
				
				var func = function () {
					_options.inProgress = false;
					
					if (typeof _options.callbackOpen == 'function')
					{
						/* Calling callback on self, so that in callback
						   'this' would reference to content node */
						_options.callbackOpen.call(this);
					}
				};
				
				if (_options.animation == 'fade') {
					/* fadeOut current tab, then fadeIn new tab */
					if (_options.curOpen)
					{
						_close.call(_options.curOpen.get(0), function () {
							
							element.fadeIn(_options.animationSpeed).addClass(_options.selectedClass).prev().addClass(_options.selectedClass);
						});
					}else{
						element.fadeIn(_options.animationSpeed).addClass(_options.selectedClass).prev().addClass(_options.selectedClass);
					}
				}else{
					/* slideDown new tab and slideUp current tab at the same time */
					element.slideDown(_options.animationSpeed, func).addClass(_options.selectedClass).prev().addClass(_options.selectedClass);
	
					if (_options.curOpen)
					{
						/* Closing currently opened tab */
						_close.call(_options.curOpen.get(0));
					}
				}
				
				
				_options.curOpen = element;
				_options.inProgress = true;
			}else{
				//Close opened tab
				if (!_options.alwaysOpen)
				{
					_close.call(_options.curOpen.get(0));
					if (!_options.preserveHeight)
					{
						_options.container.animate({height: _options.height_container + 'px'}, _options.animationSpeed);
					}
				}
			}
		};
		
		/* Close an accordion, internal_callback is called after end of anuimation */
		var _close = function (internal_callback) {
			if (_options.inProgress || _options.disabled) return;
			
			var element = $(this);
			
			_options.curOpen = null;
			_options.inProgress = true;
			
			var func = function () {
				$(this).removeClass(_options.selectedClass)
					   .prev().removeClass(_options.selectedClass);
				
				_options.inProgress = false;

				if (typeof _options.callbackClose == 'function')
				{
					_options.callbackClose.call(this);
				}
				
				if (typeof internal_callback == 'function')
				{
					internal_callback();
				}
			};
			
			if (_options.animation == 'fade') {
				element.fadeOut(_options.animationSpeed, func);
			}else{
				element.slideUp(_options.animationSpeed, func);
			}
		};
		
		$(document).unload(function () {
			if (typeof _destroy == 'function') _destroy();
		});
		
		_init();
		_init = null;
		
		var obj = {
			enable: function () { _enable(); },
			disable: function () { _disable(); },
			destroy: function () { _destroy(); }
		};
		
		/* IE6 garbage collection */
		try {
			return obj;
		} finally {
			obj = null;
		}
	}
});