;(function($) {

	$.extend($.fn, {
	
		swapClass: function(c1, c2) {
			var c1Elements = this.filter('.' + c1);
			this.filter('.' + c2).removeClass(c2).addClass(c1);
			c1Elements.removeClass(c1).addClass(c2);
			return this;
		},
		
		replaceClass: function(c1, c2) {
			return this.filter('.' + c1).removeClass(c1).addClass(c2).end();
		},
		
		hoverClass: function(className) {
			className = className || "hover";
			return this.hover(function() {
				$(this).addClass(className);
			}, function() {
				$(this).removeClass(className);
			});
		},
		
		heightToggle: function(animated, callback) {
			animated ?
				this.animate({ height: "toggle" }, animated, callback) :
				this.each(function(){
					jQuery(this)[ jQuery(this).is(":hidden") ? "show" : "hide" ]();
					if(callback)
						callback.apply(this, arguments);
				});
		},
		
		heightHide: function(animated, callback) {
			if (animated) {
				this.animate({ height: "hide" }, animated, callback);
			} else {
				this.hide();
				if (callback)
					this.each(callback);				
			}
		},
		
		prepareBranches: function(settings) {
			if (!settings.prerendered) {
				this.filter(":last-child:not(ul)").addClass(CLASSES.last);
				this.filter((settings.collapsed ? "" : "." + CLASSES.closed) + ":not(." + CLASSES.open + ")").find(">ul").hide();
			}
			return this.filter(":has(>ul)");
		},
		
		applyClasses: function(settings, toggler) {
			this.filter(":has(>ul):not(:has(>a))").find(">span").click(
				function(event) {
					if ( settings.on_select_item ) {
						var args = new Array(1);
						args[0] = $(this).parent("li").attr('id');
						settings.on_select_item.apply($(this),args);
					} else {					
						toggler.apply($(this).next());
					}	
				}
			).add( $("a", this) ).hoverClass();
			
			if (!settings.prerendered) {
				this.filter(":has(>ul:hidden)")
						.addClass(CLASSES.expandable)
						.replaceClass(CLASSES.last, CLASSES.lastExpandable);
						
				this.not(":has(>ul:hidden)")
						.addClass(CLASSES.collapsable)
						.replaceClass(CLASSES.last, CLASSES.lastCollapsable);
						
				this.prepend("<div class=\"" + CLASSES.hitarea + "\"/>").find("div." + CLASSES.hitarea).each(function() {
					var classes = "";
					$.each($(this).parent().attr("class").split(" "), function() {
						classes += this + "-hitarea ";
					});
					$(this).addClass( classes );
				});
			}
			
			this.find("div." + CLASSES.hitarea).click( toggler );
		},
		
		treeview: function(settings) {
			
			settings = $.extend({
				cookieId: "treeview"
			}, settings);
			
			if (settings.add) {
				return this.trigger("add", [settings.add]);
			}
			
			if ( settings.toggle ) {
				var callback = settings.toggle;
				settings.toggle = function() {
					return callback.apply($(this).parent()[0], arguments);
				};
			}
		
			function treeController(tree, control) {
				function handler(filter) {
					return function() {
						toggler.apply( $("div." + CLASSES.hitarea, tree).filter(function() {
							return filter ? $(this).parent("." + filter).length : true;
						}) );
						return false;
					};
				}
				$("a:eq(0)", control).click( handler(CLASSES.collapsable) );
				$("a:eq(1)", control).click( handler(CLASSES.expandable) );
				$("a:eq(2)", control).click( handler() ); 
			}
		
			function toggler() {
				$(this)
					.parent()
					.find(">.hitarea")
					.swapClass( CLASSES.collapsableHitarea, CLASSES.expandableHitarea )
					.swapClass( CLASSES.lastCollapsableHitarea, CLASSES.lastExpandableHitarea )
					.end()
					.swapClass( CLASSES.collapsable, CLASSES.expandable )
					.swapClass( CLASSES.lastCollapsable, CLASSES.lastExpandable )
					.find( ">ul" )
					.heightToggle( settings.animated, settings.toggle );
				if ( settings.unique ) {
					$(this).parent()
						.siblings()
						.find(">.hitarea")
							.replaceClass( CLASSES.collapsableHitarea, CLASSES.expandableHitarea )
							.replaceClass( CLASSES.lastCollapsableHitarea, CLASSES.lastExpandableHitarea )
						.end()
						.replaceClass( CLASSES.collapsable, CLASSES.expandable )
						.replaceClass( CLASSES.lastCollapsable, CLASSES.lastExpandable )
						.find( ">ul" )
						.heightHide( settings.animated, settings.toggle );
				}
				if ( settings.toggle ) {
					settings.toggle.apply($(this),arguments);
				}
				
			}
			
			function serialize() {
				function binary(arg) {
					return arg ? 1 : 0;
				}
				var data = [];
				branches.each(function(i, e) {
					data[i] = $(e).is(":has(>ul:visible)") ? 1 : 0;
				});
				$.cookie(settings.cookieId, data.join("") );
			}
			
			function deserialize() {
				var stored = $.cookie(settings.cookieId);
				if ( stored ) {
					var data = stored.split("");
					branches.each(function(i, e) {
						$(e).find(">ul")[ parseInt(data[i]) ? "show" : "hide" ]();
					});
				}
			}
			
			this.addClass("treeview");
			var branches = this.find("li").prepareBranches(settings);
			switch(settings.persist) {
			case "cookie":
				var toggleCallback = settings.toggle;
				settings.toggle = function() {
					serialize();
					if (toggleCallback) {
						toggleCallback.apply(this, arguments);
					}
				};
				deserialize();
				break;
			case "location":
				var current = this.find("a").filter(function() { return this.href.toLowerCase() == location.href.toLowerCase(); });
				if ( current.length ) {
					current.addClass("selected").parents("ul, li").add( current.next() ).show();
				}
				break;
			}
			
			branches.applyClasses(settings, toggler);
				
			if ( settings.control ) {
				treeController(this, settings.control);
				$(settings.control).show();
			}
			
			return this.bind("add", function(event, branches) {
				$(branches).prev()
					.removeClass(CLASSES.last)
					.removeClass(CLASSES.lastCollapsable)
					.removeClass(CLASSES.lastExpandable)
					.find(">.hitarea")
					.removeClass(CLASSES.lastCollapsableHitarea)
					.removeClass(CLASSES.lastExpandableHitarea);
				$(branches).find("li").andSelf().prepareBranches(settings).applyClasses(settings, toggler);
				if ( settings.afterload ) {
					var args = new Array(1);
					args[0] = $(branches).attr("id");
					settings.afterload.apply($(this),args);
				}
			});
		},

		loadbranch: function(settings, root, child, container) {
			$.getJSON(settings.url, {root: root}, function(response) {
				function createNode(parent) {
					var current = $("<li/>").attr("id", "LI_"+this.id || "").html("<span>" + this.text + "</span>").appendTo(parent);
					if (this.classes) {
						current.children("span").addClass(this.classes);
					}
					if (this.expanded) {
						current.addClass("open");
					}
					if (this.hasChildren || this.children && this.children.length) {
						var branch = $("<ul/>").attr("id", "UL_"+this.id || "").appendTo(current);
						if (this.hasChildren) {
							current.addClass("hasChildren");
							createNode.call({
								text:"Loading...",
								id:"placeholder",
								children:[]
							}, branch);
						}
						if (this.children && this.children.length) {
							$.each(this.children, createNode, [branch])
						}
					}
				}
				$.each(response, createNode, [child]);
        		$(container).treeview({add: child});
        		
    		});
		},
		
		reloadbranch: function(settings) {
			var UL_id = $(this).attr("id");//list
			var LI_id = $(this).parent().attr("id");//cnt
			$("#"+UL_id).remove();
			$("<ul id='"+UL_id+"' class='filetree'></ul>").appendTo("#"+LI_id);
			$("#"+LI_id).removeClass();
			$("#"+LI_id).addClass("collapsable lastCollapsable");
			$("#"+LI_id+" > div").removeClass();
			$("#"+LI_id+" > div").addClass("hitarea hasChildren-hitarea collapsable-hitarea lastCollapsable-hitarea");
			$("#"+UL_id).asynctreeview(settings);
		},
		
		asynctreeview: function(settings) {
			if (!settings.url) {
				return $(this).treeview(settings);
			}
			var container = this;
			$(this).loadbranch(settings, $(this).attr("id"), this, container);
			var userToggle    = settings.toggle;
			var userafterload = settings.afterload;
			return $(this).treeview($.extend({}, settings, {
				collapsed: true,
				toggle: function() {
					var $this = $(this);
					if ($this.hasClass("hasChildren")) {
						var childList = $this.removeClass("hasChildren").find("ul");
						childList.empty();
						$(this).loadbranch(settings, this.id, childList, container);
					}
					if (userToggle) {
						userToggle.apply(this, arguments);
					}
				},
				afterload: function() {
					if (userafterload) {
						userafterload.apply(this, arguments);
					}
				}
			})); 
		}
		
	});
	
	var CLASSES = $.fn.treeview.classes = {
		open: "open",
		closed: "closed",
		expandable: "expandable",
		expandableHitarea: "expandable-hitarea",
		lastExpandableHitarea: "lastExpandable-hitarea",
		collapsable: "collapsable",
		collapsableHitarea: "collapsable-hitarea",
		lastCollapsableHitarea: "lastCollapsable-hitarea",
		lastCollapsable: "lastCollapsable",
		lastExpandable: "lastExpandable",
		last: "last",
		hitarea: "hitarea"
	};
	
})(jQuery);
