/**
 * Ubertree jQuery
 *
 * Dependencies:
 * - jQuery core
 * - jQuery scrollTo (Optional)
 * - jQuery UI Highlight (Optional)
 */

(function($) {
	var hooks = {
		changes : []
	}
	var progress = {
		change : {}
	}
	var treeCount = 0;

	function UberTree(container, options) {
		var ubt = this;

		this.container = $(container);

		// Check for true container
		if(this.container.length != 1) {
			throw "Wrong selector!";
		}

		// Default options
		this.options = {
			url : null,
			idExtendText : false,
			itemMod : null, // Callback to modify text at list item
			icons : false,
			expand : false,
			invert : false,
			check : false,
			uncheck : false,
			listChecked : false,
			checkedUrl : null,
			highlightColor : '#2968DE', // Requires jQuery UI Highlight plugin
			type : 'none', // [checkbox|radio|none]
			post : 'ubertree',
			form : null // Optional
		}

		this.treeNum = treeCount;
		treeCount++;

		$.extend(this.options, options);

		// Ensure default highlight color
		if(typeof this.options.highlightColor != 'string') {
			this.options.highlightColor = '#2968DE';
		}

		if(typeof this.options.url == 'string' && typeof this.options.checkedUrl != 'string') {
			this.options.checkedUrl = this.options.url;
		}

		// Ensure that the form exists
		this.options.form = this.getForm();

		// Default tree class 'none'
		var treeClass = "";
		this.meta = {
			tid : this.treeNum
		}

		// Type has to be "checkbox" or "radio"
		switch(this.options.type) {
			// Extend case-list to extend the allowed widget types
			case "checkbox":
				treeClass = "checkbox";
				break;
			case "radio":
				this.options.listChecked = false;
				treeClass = "radio";
				break;
			case "none":
				// If type is 'none' some features has to be disabled
				// NOTE: can be enforced by setOption
				$.extend(this.options, {
					listChecked: false,
					check: false,
					uncheck: false,
					expand: false,
					invert: false
				});

				break;
			default:
				throw "Unknown widget type: " + this.options.type
		}

		this.formElm = $(this.options.form)
			.submit(function() {
				var submitWrapper = $('<div />')

				$(this).find('li.ubertree-item').find('.checked, .unchecked').each(function() {
					var nameVar = $(this).is('.checked') ? 'checked' : 'unchecked';

					$('<input type="hidden" />')
						.attr('name', ubt.options.post + '[' + nameVar + '][]')
						.val($(this).data('meta').id)
						.appendTo(submitWrapper);
				});

				submitWrapper.appendTo($(this));
			});

		if(this.options.listChecked) {
			this.checkedlist = $('<ul />')
				.attr('id', 'ubertree-checked-' + this.meta.tid)
				.data('meta', this.meta)
				.addClass('ubertree-checked')
				.addClass(treeClass)
				.appendTo(this.container);

			this.loadChecked();
		}
		else {
			// Failsafe
			this.checkedlist = $();
		}

		this.treelist = $('<ul />')
			.attr('id', 'ubertree-' + this.meta.tid)
			.data('meta', this.meta)
			.addClass('ubertree-tree ubertree')
			.addClass(treeClass)
			.appendTo(this.container);

		// Default hooks
		if(ubt.options.listChecked) {
			this.hook('changes', function() {
				ubt.updateCheckedTree();
			});
		}
	}

	UberTree.prototype = {
		setOptions: function(options){
			$.extend(this.options, options)
		},
		getOption: function(key){
			if(typeof this.options[key] != "undefined") {
				return this.options[key];
			}
			else {
				return false;
			}
		},
		hook: function(type, func){
			switch (type.toLowerCase()) {
				case "changes":
					this.hookChanges(func);
					break;
			}
		},
		hookChanges: function(func){
			if(typeof func == 'function') {
				hooks.changes.push(func);
			}
		},
		invoke: function(hook, items){
			switch (hook.toLowerCase()) {
				case "changes":
					this.invokeHookChanges(items);
					break;
			}
		},
		invokeHookChanges: function(items){
			for(var i = 0; i <= hooks.changes.length; i++) { // Stupid MooTools!! Messing with arrays is just wrong!
				var change = hooks.changes[i];

				if(typeof change == 'function') {
					change($(items));
				}
			}
		},
		search: function(id, callback){
			id = parseInt(id);

			if(typeof id == 'number' && !isNaN(id)) {
				this.expandTo(id, callback);
			}
			else {
				throw "Error in search!";
			}
		},
		_addCheckedRaw: function(data) {
			for(var i = 0; i < data.length; i++) {
				var child = data[i];

				var meta = {
					cid: child.id,
					tid: this.meta.tid,
					uid: this.meta.tid + '-' + child.id,
					text: child.text,
					leaf: child.leaf ? true : false
				}

				// Create fictive item
				var item = $('<li />')
					.addClass('ubertree-item')
					.data('meta', meta)

				this.addCheckedItem(item);
			}
		},
		addCheckedTree: function(items) {
			var ubt = this;

			items.each(function() {
				ubt.addCheckedItem(this);
			});
		},
		addCheckedItem: function(item){
			item = this.getEventItem(item);

			var ubt = this;
			var meta = item.data('meta');

			var ckd = $('#ubertree-checked-item-' + meta.uid);

			if(ckd.length == 0) {
				var text = $('<a href="javascript:void(0);" />')
					.text(meta.text)
					.addClass('text')
					.click(function() {
						var meta = ubt.getEventItem(this).data('meta');

						ubt.expandTo(meta.uid);
					});

				var check = $('<div />')
					.addClass('icon icon-check')
					.click(function(){
						var item = ubt.getEventItem(this);
						var meta = item.data('meta');

						var treeitem = $('#ubertree-item-' + meta.uid);

						ubt.uncheckItem(treeitem);
					});

				var icon = $('<div />')
					.addClass('icon icon-item');

				ckd = $('<li />')
					.append(check)
					.append(icon)
					.append(text)
					.data('meta', meta)
					.attr('id', 'ubertree-checked-item-' + meta.uid)
					.addClass('ubertree-checked-item checked')
					.addClass((meta.leaf ? 'leaf' : ''))
					.appendTo(this.checkedlist);
			}

			return ckd;
		},
		removeCheckedItem: function(item){
			item = this.getEventItem(item);
			var meta = item.data('meta');

			$('#ubertree-checked-item-' + meta.uid).remove();
		},
		updateCheckedTree: function(){
			var ubt = this;

			// Remove unchecked items
			this.treelist.find('li.ubertree-item.unchecked').each(function(){
				ubt.removeCheckedItem(this);
			});

			// Add checked items
			this.treelist.find('li.ubertree-item.checked').each(function(){
				ubt.addCheckedItem(this);
			});
		},
		/**
		 * Needs testing
		 *
		 * @param {Object} callback
		 */
		loadChecked: function(callback) {
			var ubt = this;

			var data = {
				getChecked : 1
			}

			$.ajax({
				type: 'GET',
				data: data,
				dataType: 'json',
				url: this.options.checkedUrl,
				success: function(data, status, XHR){
					ubt._addCheckedRaw(data);
				},
				complete: function() {
					if(typeof callback == 'function') {
						callback();
					}
				}
			});
		},
		addTreeItem: function(parent, child){
			if(parent === null || typeof parent == 'undefined') {
				parent = this.treelist;
			}
			if(!(parent = this.getChildTree(parent))) {
				throw "No parent object found!";
				return;
			}

			var item = this.getItem(child.id);

			if(item.length == 1) {
				var c = item;
			}
			else {
				var meta = {}
				$.extend(meta, child);
				$.extend(meta, {
					cid: child.id,
					tid : this.meta.tid,
					uid : this.meta.tid + '-' + child.id,
					text: child.text,
					leaf: child.is_leaf ? true : false
				});

				if(loadedItem = this.getEventItem(parent)) {
					loadedItem.addClass('loaded');
				}

				// Default text item
				var text = {
					data: $('<a href="javascript:void(0);" />')
						.addClass('text')
						.text(child.text),
					meta: meta
				}

				// Define text node at list item
				if(typeof this.options.itemMod == 'function') {
					this.options.itemMod(text);
				}

				var ubt = this;

				var textNode = $('<div />')
					.addClass('text-item')
					.append((!text.data ? child.text : text.data));

				if(this.options.type != 'none') {
					var check = $('<div />').addClass('icon icon-check');

					$(textNode).add(check).click(function(){
						if (ubt.options.type == 'radio') {
							ubt.checkItem(this);
						}
						else {
							ubt.invertItem(this);
						}
					});
				}

				var expand = $('<div />').addClass('icon icon-expand')
				if(!child.is_leaf) {
					expand.click(function(){
						ubt.expandCollapsItemToggle(this);
					});
				}

				if(this.options.icons) {
					var icon = $('<div />').addClass('icon icon-item');
				}

				var c = $('<li />')
					.attr('id', 'ubertree-item-' + meta.uid)
					.addClass('ubertree-item')
					.data('meta', meta)
					.append(expand)
					.append(check)
					.append(icon)
					.append(textNode)
					.appendTo(parent)

				// Is leaf
				if(child.is_leaf) {
					c.addClass('leaf');
				}

				// Is selected
				if(child.selected == true && this.options.type != 'none') {
					this.checkItem(c);
				}

				if((this.options.expand || (this.options.expand && this.options.check)) && !child.is_leaf) {
					var expText = this.options.check && this.options.type == 'checkbox' ? 'Check' : 'Expand';

					$('<a href="javascript:void(0)" />').text(expText).addClass('feature').click(function(){
						var item = this;

						ubt.expandTree(this, function(){
							if(ubt.options.type == 'checkbox') {
								ubt.checkTree(item);
							}
						});
					}).appendTo(c);
				}

				if(this.options.invert && !child.is_leaf && this.options.type == 'checkbox') {
					$('<a href="javascript:void(0)" />')
						.text('Invert')
						.addClass('feature')
						.click(function(){
							ubt.invertTree(this);
						}).appendTo(c);
				}

				if(this.options.uncheck && !child.is_leaf && this.options.type == 'checkbox') {
					$('<a href="javascript:void(0);" />')
						.text('Uncheck')
						.addClass('feature')
						.click(function(){
							ubt.uncheckTree(this);
						}).appendTo(c);
				}
			}

			return c;
		},
		addTreeItems: function(parent, children, callback){
			if(typeof children == 'object' && children != null) { //null check: #4836 @klah + haan
				for (var i = 0; i < children.length; i++) { // Stupid MooTools!! Messing with arrays is just wrong!
					var child = children[i];

					var item = this.addTreeItem(parent, child);

					if(typeof child.children == 'object') {
						this.addTreeItems(item, child.children);
					}
				}

				if(typeof callback == 'function') {
					callback();
				}
			}
		},
		getForm: function(){
			if(this.options.form) {
				return this.options.form;
			}
			else {
				var form = this.container;

				while (!form.is('form')) {
					form = form.parent();

					if(form.is('body')) {
						break;
					}
				}

				if(form.is('form')) {
					return this.options.form = form;
				}
				else {
					return $('<form/>'); // Read-only tree fallback
				}
			}
		},
		getItem: function(token) {
			var item; // Declaration

			if((item = $('li#ubertree-item-' + token)).length == 1) {
				return item;
			}
			else if((item = $('li#ubertree-item-' + this.meta.tid + '-' + token)).length > 0) {
				if(item.length == 1) {
					return item;
				}
				else {
					throw "Item dublicate found!";
				}

				return item;
			}
			else if(typeof token == 'number' && !isNaN(token)) {
				return false;
			}
			else if((item = this.getEventItem(token)).length == 1) {
				return item;
			}

			return false;
		},
		getEventItem: function(obj){
			var item = $(obj);

			var failSelector = 'ul#ubertree-' + this.meta.tid + ', ul#ubertree-checked-' + this.meta.tid + ', body';

			while(!item.is('li.ubertree-item, li.ubertree-checked-item')) {
				item = item.parent();

				if(item.is(failSelector) || item.find(failSelector).length > 0) {
					return false;
				}
			}

			return item;
		},
		getParent: function(obj) {
			var item = this.getEventItem(obj);

			if(item) {
				return this.getEventItem(item.parent());
			}

			return false;
		},
		getChildTree: function(tree){
			tree = $(tree);

			if(tree.is('ul.ubertree-tree')) {
				return tree;
			}
			else {
				var ul = tree.find('> ul.ubertree-tree:eq(0)');

				if(ul.length) {
					return ul;
				}
				else
					if(tree.is('li.ubertree-item')) {
						ul = $('<ul />').addClass('ubertree-tree').appendTo(tree);

						return ul;
					}
			}

			return false;
		},
		expandItems: function(items, tree, callback){
			var ubt = this;

			$(items).each(function(){
				ubt.expandItem(this, tree, callback);
			});
		},
		expandTree: function(obj, callback){
			this.expandItem(obj, true, callback);
		},
		expandItem: function(obj, tree, callback){
			var li = this.getEventItem(obj);

			if(li) {
				var ubt = this;
				var meta = li.data('meta');

				var params = {
					id: meta.cid,
					tree: tree ? 1 : 0
				}

				if(!li.is('.loaded,.leaf')|| tree) {
					ubt.load(params, li, function(){
						if(tree) {
							var items = li.find('li.ubertree-item').andSelf();
						}
						else {
							var items = li;
						}

						items.addClass('loaded expanded');

						if(typeof callback == 'function') {
							callback();
						}
					});
				}
				else {
					li.addClass('expanded');
				}

				this.invoke('changes', li);
			}
		},
		expandTo: function(obj, callback, secondRun) {
			var item = this.getItem(obj);
			var leaf = item;

			function returnCallback(item) {
				if(typeof callback == 'function') {
					callback(item);
				}
			}

			if(item.length == 1) {
				while(item != false) {
					item = this.getParent(item);

					if(item) {
						this.expandItem(item);
					}
				}

				this._scrollTo(leaf);
				this._highlightItem(leaf);

				returnCallback(leaf);
			}
			else if(typeof this.options.url != 'string') {
				throw "No such item";
			}
			else if(typeof obj == 'number' && !secondRun) {
				var ubt = this;

				var params = {
					searchId : obj
				}

				this.load(params, this.treelist, function() {
					ubt.expandTo(obj, callback, true);
				});
			}
			else if(secondRun) {
				returnCallback();
			}
		},
		collapsItem: function(obj){
			var li = this.getEventItem(obj);

			li.removeClass('expanded');

			this.invoke('changes', li);
		},
		expandCollapsItemToggle: function(obj, tree){
			var li = this.getEventItem(obj);

			if(!li.is('.expanded')) {
				this.expandItem(li, tree);
			}
			else {
				this.collapsItem(li);
			}
		},
		invertTree: function(obj){
			var ubt = this;
			var li = this.getEventItem(obj);

			li.find('li.ubertree-item').andSelf().each(function(){
				ubt.invertItem(this);
			});
		},
		invertItem: function(obj){
			var li = this.getEventItem(obj);

			if(!li.is('.checked')) {
				this.checkItem(li);
			}
			else {
				this.uncheckItem(li);
			};

			this.invoke('changes', li);
		},
		checkTree: function(items){
			this.checkItem(items, true);
		},
		checkItem: function(items, tree){
			var ubt = this;
			var li = this.getEventItem(items);

			if(this.options.type == 'radio') {
				this.container.find('li.ubertree-item.checked').each(function(){
					ubt.uncheckItem(this);
				});
			}

			if(tree) {
				li = li.find('li.ubertree-item:not(.checked)').andSelf();
			}

			li.removeClass('unchecked').addClass('checked');

			this.invoke('changes', li);
		},
		uncheckTree: function(items){
			this.uncheckItem(items, true);
		},
		uncheckItem: function(obj, tree){
			var li = this.getEventItem(obj);

			if(tree) {
				var items = li.find('li.ubertree-item.checked');

				if(li.is('.checked')) {
					items = items.add(li);
				}

				li = items;
			}

			li.removeClass('checked').addClass('unchecked');

			this.invoke('changes', li);
		},
		_scrollTo: function(item) {
			item = this.getItem(item);

			if(this.treelist.scrollTo) {
				this.treelist.scrollTo(item);
			}
		},
		_highlightItem: function(obj) {
			var item = this.getEventItem(obj);

			if(item && item.effect) {
				item.find('> a.text').effect('highlight', {
					color: this.options.highlightColor
				}, 1300);
			}
		},
		load: function(params, parent, callback){
			var ubt = this;

			// Force use of url
			if(typeof this.options.url != 'string') {
				throw "Missing URL for tree backend";
			}

			var data = {
				tree: 0
			}

			$.extend(data, params)

			if(!parent) {
				parent = this.treelist;
			}

			$.ajax({
				type: 'GET',
				data: data,
				dataType: 'json',
				url: this.options.url,
				beforeSend: function(){
					parent.addClass('loading');
				},
				success: function(data, status, XHR){
					ubt.addTreeItems(parent, data, callback);
				},
				complete: function(){
					parent.removeClass('loading');
				}
			});
		}
	}

	window.UberTree = UberTree;
})(jQuery);

