(function ($) {

    $.ui = $.ui || {};

    $.fn.extend({
        tree: function (options){
            if (!this.is(".ui-tree")) {
                return new $.ui.tree(this, options);
            }
        }
    });


    $.ui.tree = function (element, options) {
        var el = element,
            obj = this,
            origin = el,
            args = arguments;
        // if element is not a list, create one and it into the element
        if (!(/^[ou]l$/i.test(el[0].tagName))) {
            if (!options.url) {
                return false;
            }
            el.html("<ul></ul>");
            el = $("ul", el);
        }
        el.addClass("ui-tree");

        // private varibles
        var tree = {
            list: el,
            lis: [],
            dim: el.offset(),
            points: [],
            bef: {y: 0, num: 0},
            win: $(window),
            timer: null,
            prev: 0,
            events: {
                grab: function () {},
                drag: function () {},
                drop: function () {},
                append: function () {},
                insertabove: function () {},
                insertbelow: function () {},
                load: function () {},
                nodeover: function () {},
                nodeout: function () {},
                onready: function () {}
            }
        };

        this.options = options;
        // These functions could be usefull for debug
        // this.getLis = function () {
        //     return tree.lis;
        // };
        // this.getPoints = function () {
        //     return tree.points;
        // };
        // this.update = function () {
        //     updatePoints();
        // };
        this.expandPath = function () {
            var attr = [],
            f = function () {};
            for (var i = 0, ii = arguments.length; i < ii; i++) {
                if (typeof arguments[i] == "function") {
                    f = arguments[i];
                } else {
                    attr.push(arguments[i]);
                }
            }
            if (attr.length) {
                var n = 1, node;
                for (var name in attr[0]) {
                    node = this.findNodeBy(name, attr[0][name]);
                    break;
                }
                node.open(function () {
                    if (n < attr.length) {
                        for (name in attr[n]) {
                            node = obj.findNodeBy(name, attr[n][name]);
                            if (node) {
                                break;
                            }
                        }
                        n++;
                        node.open(arguments.callee);
                    } else {
                        f();
                    }
                });
            } else {
                f();
            }
        };
        this.reload = function (options) {
            if (origin != el) {
                el.remove();
            }
            for (var i in options) {
                this.options[i] = options[i];
            }
            return new args.callee(element, this.options);
        };
        this.append = function (node) {
            var newnode = createNode(node);
            el.append(newnode);
            prepareItem.call(newnode);
            updateList();
        };

        this.findNodeBy = function (attributeName, attributeValue) {
            var results = [], lis = el[0].getElementsByTagName("li");
            for (var i = 0, ii = lis.length; i < ii; i++) {
                if (lis[i][attributeName] == attributeValue) {
                    var $li = $(lis[i]);
                    var res = {
                        "0": lis[i],
                        $: $li,
                        text: $li.find("span").html(),
                        href: $li.find("a").attr("href"),
                        linkClass: $li.find("a").attr("class"),
                        nodeClass: $li.attr("class"),
                        open: function (callback) {
                            return tree.lis[this[0].num].open(callback);
                        },
                        close: function () {
                            tree.lis[this[0].num].close();
                        },
                        getAttribute: function (attribute){
                            return this[0][attribute];
                        },
                        setAttribute: function (attribute, value){
                            this[0][attribute] = value;
                        },
                        highlight: function () {
                            this.$.addClass("highlighted");
                        },
                        makeDraggable: function () {
                            this.setAttribute("undraggable", false);
                            this.$.removeClass("undraggable");
                        },
                        makeUndraggable: function () {
                            this.setAttribute("undraggable", true);
                            this.$.addClass("undraggable");
                        },
                        setText: function(nodeText) {
                        	this.text = nodeText;
                        	this[0].text = nodeText;
                            this.$.find("span").html(nodeText);
                        },
                        append: function (node) {
                            var uls = this.$.find("ul");
                            if (!uls.length) {
                                if (this[0].toBeLoaded) {
                                    var theNode = this;
                                    this.open(function () {theNode.append(node);});
                                    return false;
                                }
                                this.$.append("<ul></ul>");
                                uls = this.$.find("ul");
                            }
                            var newnode = createNode(node);
                            uls.append(newnode);
                            prepareItem.call(newnode);
                            if (typeof this[0].closed == "undefined") {
                                this.$.addClass("closed");
                                this[0].closed = true;
                                uls.hide();
                            }
                            updateList();
                        },
                        below: function (node) {
                            var newnode = createNode(node);
                            this.$.after(newnode);
                            prepareItem.call(newnode);
                            updateList();
                        },
                        above: function (node) {
                            var newnode = createNode(node);
                            this.$.before(newnode);
                            prepareItem.call(newnode);
                            updateList();
                        },
                        remove: function () {
                            this.$.remove();
                            updateList();
                        },
                        reload: function () {
                            if (this[0].getElementsByTagName("ul").length) {
                                this[0].removeChild(this[0].getElementsByTagName("ul")[0]);
                                this.$.removeClass("opened").addClass("closed");
                                this[0].closed = true;
                                tree.lis[this[0].num].open();
                            }
                        }
                    };
                    if (obj.options.parameters && obj.options.parameters.length) {
                        for (var j = 0, jj = obj.options.parameters.length; j < jj; j++) {
                            if (lis[i][obj.options.parameters[j]]) {
                                res[obj.options.parameters[j]] = lis[i][obj.options.parameters[j]];
                            }
                        }
                    }
                    results.push(res);
                }
            };

            if (results.length == 0) {
                return null;
            } else if (results.length == 1) {
                return results[0];
            } else {
                return results;
            }
        };

        if (options.url) {
            // Spinner
            var div = document.createElement("div");
            div.className = "tree-spinner";
            $("body").append(div);
            tree.spinner = $(div).spinner();
            tree.spinner.hide();
        }

        // copy event handlers from options to tree.events
        for (var i in tree.events) {
            if (typeof options[i] == "function") {
                tree.events[i] = options[i];
            }
        }

        function Item(li) {
            this.element = $(li);
            this.height = this.element.height();
            this.append = function (li) {
                if (this.element[0] == li) {
                    return false;
                }
                if (this.element[0].toBeLoaded) {
                    var item = this;
                    this.load(function () {item.append(li);});
                    return false;
                }
                if (this.element[0].tagName.toLowerCase() == "li") {
                    var ul = $("ul:first", this.element);
                    var grandpa = li.parentNode.parentNode;
                    if (ul.length) {
                        ul.prepend(li);
                    } else {
                        ul = document.createElement("ul");
                        ul.appendChild(li);
                        this.element[0].appendChild(ul);
                        this.element.addClass("opened");
                        $(".click-zone:first", this.element).css("display", "inline");
                    }
                    if (grandpa.tagName.toLowerCase() == "li" && $("li", grandpa).length < 2) {
                        tree.lis[grandpa.num].notaFolderAnymore();
                    }
                    // updateList();
                    setTimeout(updateList, 0);
                    tree.events.append.call({source: li, target: this.element[0]});
                }
            };
            this.below = function (li) {
                var grandpa = li.parentNode.parentNode;
                this.element.after(li);
                if (grandpa.tagName.toLowerCase() == "li" && $("li", grandpa).length < 2) {
                    tree.lis[grandpa.num].notaFolderAnymore();
                }
                // updateList();
                setTimeout(updateList, 0);
                tree.events.insertbelow.call({source: li, target: this.element[0]});
            };
            this.above = function (li) {
                var grandpa = li.parentNode.parentNode;
                this.element.before(li);
                if (grandpa.tagName.toLowerCase() == "li" && $("li", grandpa).length < 2) {
                    tree.lis[grandpa.num].notaFolderAnymore();
                }
                // updateList();
                setTimeout(updateList, 0);
                tree.events.insertabove.call({source: li, target: this.element[0]});
            };
            this.open = function (callback) {
                callback = callback || function () {};
                if (this.element.hasClass("closed")) {
                    var ul = this.element.contents().filter("ul:has(li)");
                    if (ul.length) {
                        ul.show();
                        this.closed = false;
                        this.element.removeClass("closed").addClass("opened");
                        updateList();
                        callback(true);
                        return true;
                    } else {
                        return this.load(callback);
                    }
                }
                callback(false);
                return false;
            };
            this.close = function (callback) {
                callback = callback || function () {};
                var ul = this.element.contents().filter("ul:has(li)");
                if (ul.length) {
                    ul.hide();
                    this.closed = true;
                    this.element.removeClass("opened").addClass("closed");
                    tree.lis.splice(this.element[0].num + 1, ul[0].getElementsByTagName("li").length);
                    updateList();
                    callback();
                }
            };
            this.load = function (callback) {
                var url = obj.options.url;
                if (!url) {
                    return false;
                }
                callback = callback || function () {};
                this.element[0].toBeLoaded = false;
                this.element[0].closed = true;
                if (options.parameters && options.parameters.length) {
                    for (var i = 0, ii = options.parameters.length; i < ii; i++) {
                        url += (i?"&":"?") + options.parameters[i] + "=" + (this.element[0][options.parameters[i]] || "");
                    }
                }
                var node = this,
                    span = this.element[0].getElementsByTagName("span")[0],
                    spanWidth = span.offsetWidth,
                    spanLeft = $(span).offset().left;
                node.loading = true;
                tree.spinner.putInBox({x:spanLeft + spanWidth, y: this.top, width: 25, height: tree.H});
                tree.spinner.show();
                $.getJSON(url, function (data) {
                    var ul = node.element.contents().filter("ul");
                    if (!ul.length) {
                        ul = document.createElement("ul");
                        node.element[0].appendChild(ul);
                        ul = $(ul);
                    }
                    for (var i = 0, ii = data.length; i < ii; i++) {
                        var li = createNode(data[i]);
                        ul[0].appendChild(li);
                        prepareItem.call(li);
                    }
                    ul.hide();
                    //  this will clear the loading flag
                    node.open(callback);
                    tree.events.load();
                    tree.spinner.hide();
                });
                return true;
            };
            this.notaFolderAnymore = function () {
                this.element.removeClass("closed").removeClass("opened");
                $(".click-zone:first", this.element).hide();
                var ul = this.element[0].getElementsByTagName("ul");
                this.closed = false;
                if (ul.length) {
                    this.element[0].removeChild(ul[0]);
                }
            };
        }
        function getItem(num) {
            var p = tree.points[num];
            if (typeof p != "undefined") {
                return {el: tree.lis[p.num], where: p.where, top: p.top};
            } else {
                return {el: new Item(tree.list[0]), where: "append", top: tree.dim.top};
            }
        }
        function updatePoints() {
            tree.bef = {y: 0, num: 0};
            tree.points = [];
            for (var j = 0, jj = tree.lis.length; j < jj; j++) {
                var offset = tree.lis[j].element.offset(),
                    y = offset.top;
                tree.lis[j].top = y;
                tree.lis[j].left = offset.left;
                if (tree.bef.y) {
                    var q = (y - tree.bef.y) / 4;
                    for (var i = tree.bef.y; i < y; i++) {
                        var where = (i - tree.bef.y < q)?"above":(i - tree.bef.y < q * 3)?"append":"below";
                        tree.points[i] = {num: tree.bef.num, where: where, top: tree.bef.y};
                    }
                }
                if (j == jj - 1) {
                    var q = (tree.lis[j].height) / 4;
                    for (var i = y; i < y + tree.lis[j].height; i++) {
                        var where = (i - y < q)?"above":(i - y < q * 3)?"append":"below";
                        tree.points[i] = {num: j, where: where, top: y};
                    }
                }
                tree.bef.y = y;
                tree.bef.num = j;
            }
        }
        function updateList() {
            tree.lis = [];
            var nodes = tree.list[0].getElementsByTagName("li");
            for (var i = 0, ii = nodes.length; i < ii; i++) {
                if (nodes[i].offsetHeight > 0 && !$(nodes[i]).hasClass("tree-helper")) {
                    nodes[i].num = tree.lis.length;
                    tree.lis.push(new Item(nodes[i]));
                }
            }
            updatePoints();
        }
        this.updateList = updateList;

        var draggableOptions = function() { return {
                distance: 3,
                helper: function () {
                    return $(this).clone().append("<b></b>").addClass("tree-helper");
                },
                cursorAt: {top: tree.H / 2, left: 30},
                // containment: el,
                stop: function(e, ui) {
                    clearInterval(tree.timer);
                    clearTimeout(tree.opentimer);
                    tree.opentimer = null;
                    var item = getItem(tree.prev);
                    item.el.element.removeClass("over").removeClass("above").removeClass("append").removeClass("below");
                    item.el.element.next().removeClass("over").removeClass("above").removeClass("append").removeClass("below");
                    tree.win.unbind("keydown", tree.escape);
                    delete tree.escape;
                    if (ui.instance.options.revert) {
                        ui.instance.options.revert = false;
                        return false;
                    }
                    item = getItem(e.pageY);
                    // check if the target is the source or it's children
                    var ele = item.el.element[0], isOk = true;
                    while (ele != el[0]) {
                        if (ele == ui.instance.element[0]) {
                            isOk = false;
                            break;
                        }
                        ele = ele.parentNode;
                    }
                    // don't insert above next element and don't append to element's parent
                    isOk =  isOk && !(item.where == "above" && item.el.element.prev()[0] == ui.instance.element[0]) &&
                            !(item.where == "append" && item.el.element[0] == ui.instance.element[0].parentNode.parentNode);
                    if (isOk) {
                        item.el[item.where](ui.instance.element[0]);
                        tree.events.drop.call({position: item.where, source: ui.instance.element[0], target: item.el.element[0]});
                    }
                },
                start: function (e, ui) {
                    tree.events.grab.call(ui.instance.element[0]);
                    if (ui.instance.element[0].undraggable) {
                        ui.helper.addClass("no");
                        ui.instance.options.revert = true;
                    }
                    tree.escape = function (e) {
                        if (e.keyCode == 27) {
                            var item = getItem(tree.prev);
                            item.el.element.removeClass("over").removeClass("above").removeClass("append").removeClass("below");
                            item.el.element.next().removeClass("over").removeClass("above").removeClass("append").removeClass("below");
                            var newhelper = ui.helper.clone();
                            ui.helper.before(newhelper);
                            newhelper.animate({left: ui.instance.elementOffset.left + "px", top: ui.instance.elementOffset.top + "px", opacity: 0}, "slow", "swing", function () {newhelper.remove();});
                            ui.helper.css("display", "none");
                            ui.instance.options.revert = true;
                        }
                    };
                    tree.win.keydown(tree.escape);
                },
                drag: function (e, ui) {
                    var olditem = getItem(tree.prev);
                    olditem.el.element.removeClass("above").removeClass("append").removeClass("below");
                    olditem.el.element.next().removeClass("above").removeClass("append").removeClass("below");
                    if (!ui.instance.options.revert || tree.out) {
                        tree.prev = e.pageY;
                        var item = getItem(tree.prev);
                        if (item.el.element[0] == el[0]) {
                            ui.instance.options.revert = true;
                            tree.out = true;
                            return;
                        } else {
                            if (tree.out) {
                                tree.out = false;
                                ui.instance.options.revert = false;
                            }
                        }
                        if (item.el != olditem.el) {
                            tree.events.nodeout.call(olditem.el.element);
                            if (tree.opentimer) {
                                clearTimeout(tree.opentimer);
                                tree.opentimer = false;
                            }
                        }
                        tree.events.nodeover.call({element: item.el.element, position: item.where});
                        var className = item.where,
                            next = item.el.element.next();
                        if (className == "below" && next.length && !next.hasClass("tree-helper")) {
                            next.addClass("above");
                        } else {
                            getItem(tree.prev).el.element.addClass(className);
                        }
                        // Openning
                        if (item.where == "append" && (item.el.closed || item.el.element[0].toBeLoaded) && !tree.opentimer) {
                            tree.opentimer = (function (item) {
                                return setTimeout(function () {
                                    item.el.element.removeClass("append");
                                    item.el.open(function () {tree.opentimer = false;});
                                }, 500);
                            })(item);
                        }
                        // Scrolling
                        var f = arguments.callee;
                        if (tree.win.height() - e.pageY + tree.win.scrollTop() < 30) {
                            clearInterval(tree.timer);
                            tree.timer = setInterval(function () {
                                window.scrollBy(0, 4);
                                ui.helper.css("top", parseInt(ui.helper.css("top")) + 4 + "px");
                                f({pageY: e.pageY + 4}, ui);
                            }, tree.win.height() - e.pageY + tree.win.scrollTop());
                        } else {
                            if (tree.win.scrollTop() > 0 && (e.pageY - tree.win.scrollTop()) < 30) {
                                clearInterval(tree.timer);
                                tree.timer = setInterval(function () {
                                    window.scrollBy(0, -4);
                                    f({pageY: e.pageY - 4}, ui);
                                    ui.helper.css("top", parseInt(ui.helper.css("top")) - 4 + "px");
                                }, e.pageY - tree.win.scrollTop());
                            } else {
                                if (tree.timer) {
                                    clearInterval(tree.timer);
                                }
                            }
                        }
                        tree.events.drag.call({element: ui.instance.element[0], left: e.pageX, top: e.pageY});
                    }
                }
            };
        };

        function prepareItem() {
            var node = $(this);
            node.draggable(draggableOptions());
             if (node.hasClass("undraggable") || obj.options.undraggable) {
                node[0].undraggable = true;
            }
            var a = $(this.getElementsByTagName("a")[0]);
            a.click(function (e) {
                e.stopPropagation();
            });
            if (obj.options.unclickable) {
                node.addClass("unclickable");
            }
            if (node.hasClass("unclickable")) {
                a.click(function (e) {
                    e.preventDefault();
                });
            }
            a.keydown(function (e) {
                var item = tree.lis[node[0].num];
                if (e.keyCode == 39 || e.keyCode == 37) { // right or left
                	$(this).prev().click();
                }
                if (e.keyCode == 40) { // down
                    if (node[0].num + 1 < tree.lis.length) {
                        tree.lis[node[0].num + 1].element[0].getElementsByTagName("a")[0].focus();
                    }
                }
                if (e.keyCode == 38) { // up
                    if (node[0].num - 1 >= 0) {
                        tree.lis[node[0].num - 1].element[0].getElementsByTagName("a")[0].focus();
                    }
                }
            });
        };

        $.ui.tree.callNumber = 0;
        function createNode(node) {
            var li = document.createElement("li");
            li.className = node.nodeClass;
            if (obj.options.parameters && obj.options.parameters.length) {
                for (var j = 0, jj = obj.options.parameters.length; j < jj; j++) {
                    if (node[obj.options.parameters[j]]) {
                        li[obj.options.parameters[j]] = node[obj.options.parameters[j]];
                    }
                }
            }
            var a = document.createElement("a");
            var span = document.createElement("span");
            a.href = node.href;
            span.appendChild(document.createTextNode(node.text));
            a.appendChild(span);
            a.className = node.linkClass;
            clickZone = document.createElement("div");
            $(clickZone).addClass("click-zone");
            $(clickZone).click(function (e) {
                e.stopPropagation();
            	if (tree.lis[this.parentNode.num].loading){
            		return;
            	}
                if ($(this.parentNode).hasClass("closed")) {
                    tree.lis[this.parentNode.num].open();
                } else {
                    tree.lis[this.parentNode.num].close();
                }
            });
            li.appendChild(clickZone);
            li.appendChild(a);
            if ($(li).hasClass("opened")) {
                $(li).removeClass("opened").addClass("closed");
                li.closed = true;
            } else if ($(li).hasClass("closed")) {
                li.toBeLoaded = true;
            } else {
                $(clickZone).css("display", "none");
            }
            return li;
        }

        var li = el.contents().filter("li");
        if (li.length > 0) {
            tree.H = li.height();
            li.each(prepareItem);
            updateList();
            tree.events.onready.call(this);
        } else {
            var url = obj.options.initUrl || obj.options.url;
            if (!url) {
                return false;
            }
            tree.spinner.putInBox({x: tree.dim.left, y: tree.dim.top, width: 16, height: 16});
            tree.spinner.show();
            var call = ++$.ui.tree.callNumber;
            $.getJSON(url, function (data) {
                for (var i = 0, ii = data.length; i < ii; i++) {
                    var li = createNode(data[i]);
                    el[0].appendChild(li);
                    if (i == 0) {
                        tree.H = $(li).height();
                    }
                    prepareItem.call(li);
                }
                updateList();
                tree.spinner.hide();
                // if the data was re-requested again we only call onready for the latest request
                if (call == $.ui.tree.callNumber){
                    tree.events.onready.call(this);
                    $.ui.tree.callNumber = 0;
                }
            });
        }
        tree.offset = el[0].offsetTop;
        setInterval(function () {
            if (el[0].offsetTop != tree.offset) {
                updatePoints();
                tree.offset = el[0].offsetTop;
            }
        }, 10);
        return this;
    };
})(jQuery);

// $(function () {
//     tree = $("#anotherList").tree({url: "json2.js", initUrl: "json.js", parameters: ["pageId"], undraggable: false});
// });
