/**
 * Copyright JS Foundation and other contributors, http://js.foundation
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 **/
(function($) {

/**
 * options:
 *   - data : array - initial items to display in tree
 *   - multi : boolean - if true, .selected will return an array of results
 *                       otherwise, returns the first selected item
 *   - sortable: boolean/string - TODO: see editableList
 *   - selectable: boolean - default true - whether individual items can be selected
 *   - rootSortable: boolean - if 'sortable' is set, then setting this to
 *                             false, prevents items being sorted to the
 *                             top level of the tree
 *   - autoSelect: boolean - default true - triggers item selection when navigating
 *                           list by keyboard. If the list has checkboxed items
 *                           you probably want to set this to false
 *
 * methods:
 *   - data(items) - clears existing items and replaces with new data
 *   - clearSelection - clears the selected items
 *   - filter(filterFunc) - filters the tree using the provided function
 * events:
 *   - treelistselect : function(event, item) {}
 *   - treelistconfirm : function(event,item) {}
 *   - treelistchangeparent: function(event,item, oldParent, newParent) {}
 *
 * data:
 * [
 *     {
 *         label: 'Local', // label for the item
 *         sublabel: 'Local', // a sub-label for the item
 *         icon: 'fa fa-rocket', // (optional) icon for the item
 *         checkbox: true/false, // (optional) if present, display checkbox accordingly
 *         radio: 'group-name',  // (optional) if present, display radio box - using group-name to set radio group
 *         selected: true/false, // (optional) whether the item is selected or not
 *         children: [] | function(done,item) // (optional) an array of child items, or a function
 *                                       // that will call the `done` callback with an array
 *                                       // of child items
 *         expanded: true/false, // show the child items by default
 *         deferBuild: true/false, // don't build any ui elements for the item's children
 *                                    until it is expanded by the user.
 *         element: // custom dom element to use for the item - ignored if `label` is set
 *         collapsible: true/false, // prevent a parent item from being collapsed. default true.
 *     }
 * ]
 *
 * var treeList = $("<div>").css({width: "100%", height: "100%"}).treeList({data:[...]})
 * treeList.on('treelistselect', function(e,item) { console.log(item)})
 * treeList.treeList('data',[ ... ] )
 *
 *
 * After `data` has been added to the tree, each item is augmented the following
 * properties and functions:
 *
 *   item.parent - set to the parent item
 *   item.depth - the depth in the tree (0 == root)
 *   item.treeList.container
 *   item.treeList.label - the label element for the item
 *   item.treeList.parentList  - the editableList instance this item is in
 *   item.treeList.remove(detach) - removes the item from the tree. Optionally detach to preserve any event handlers on the item's label
 *   item.treeList.makeLeaf(detachChildElements) - turns an element with children into a leaf node,
 *                                                 removing the UI decoration etc.
 *                                                 detachChildElements - any children with custom
 *                                                 elements will be detached rather than removed
 *                                                 so jQuery event handlers are preserved in case
 *                                                 the child elements need to be reattached later
 *   item.treeList.makeParent(children) - turns an element into a parent node, adding the necessary
 *                                        UI decoration.
 *   item.treeList.insertChildAt(newItem,position,select) - adds a child item an the specified position.
 *                                                          Optionally selects the item after adding.
 *   item.treeList.addChild(newItem,select) - appends a child item.
 *                                            Optionally selects the item after adding.
 *   item.treeList.expand(done) - expands the parent item to show children. Optional 'done' callback.
 *   item.treeList.collapse() - collapse the parent item to hide children.
 *   item.treeList.sortChildren(sortFunction) - does a one-time sort of the children using sortFunction
 *   item.treeList.replaceElement(element) - replace the custom element for the item
 *
 *
 */

    $.widget( "nodered.treeList", {
        _create: function() {
            var that = this;
            var autoSelect = true;
            if (that.options.autoSelect === false) {
                autoSelect = false;
            }
            this.element.addClass('red-ui-treeList');
            this.element.attr("tabIndex",0);
            var wrapper = $('<div>',{class:'red-ui-treeList-container'}).appendTo(this.element);
            this.element.on('keydown', function(evt) {
                var focussed = that._topList.find(".focus").parent().data('data');
                if (!focussed && (evt.keyCode === 40 || evt.keyCode === 38)) {
                    if (that._data[0]) {
                        if (autoSelect) {
                            that.select(that._data[0]);
                        } else {
                            that._topList.find(".focus").removeClass("focus")
                        }
                        that._data[0].treeList.label.addClass('focus')
                    }
                    return;
                }
                var target;
                switch(evt.keyCode) {
                    case 32: // SPACE
                    case 13: // ENTER
                        if (!that.options.selectable) { return }
                        if (evt.altKey || evt.ctrlKey || evt.metaKey || evt.shiftKey) {
                            return
                        }
                        evt.preventDefault();
                        evt.stopPropagation();
                        if (focussed.checkbox) {
                            focussed.treeList.checkbox.trigger("click");
                        } else if (focussed.radio) {
                            focussed.treeList.radio.trigger("click");
                        } else if (focussed.children) {
                            if (focussed.treeList.container.hasClass("expanded")) {
                                focussed.treeList.collapse()
                            } else {
                                focussed.treeList.expand()
                            }
                        } else {
                            that._trigger("confirm",null,focussed)
                        }
                    break;
                    case 37: // LEFT
                        evt.preventDefault();
                        evt.stopPropagation();
                        if (focussed.children&& focussed.treeList.container.hasClass("expanded")) {
                            focussed.treeList.collapse()
                        } else if (focussed.parent) {
                            target = focussed.parent;
                        }
                    break;
                    case 38: // UP
                        evt.preventDefault();
                        evt.stopPropagation();
                        target = that._getPreviousSibling(focussed);
                        if (target) {
                            target = that._getLastDescendant(target);
                        }
                        if (!target && focussed.parent) {
                            target = focussed.parent;
                        }
                    break;
                    case 39: // RIGHT
                        evt.preventDefault();
                        evt.stopPropagation();
                        if (focussed.children) {
                            if (!focussed.treeList.container.hasClass("expanded")) {
                                focussed.treeList.expand()
                            }
                        }
                    break
                    case 40: //DOWN
                        evt.preventDefault();
                        evt.stopPropagation();
                        if (focussed.children && Array.isArray(focussed.children) && focussed.children.length > 0 && focussed.treeList.container.hasClass("expanded")) {
                            target = focussed.children[0];
                        } else {
                            target = that._getNextSibling(focussed);
                            while (!target && focussed.parent) {
                                focussed = focussed.parent;
                                target = that._getNextSibling(focussed);
                            }
                        }
                    break
                }
                if (target) {
                    if (autoSelect) {
                        that.select(target);
                    } else {
                        that._topList.find(".focus").removeClass("focus")
                    }
                    if (target.treeList.label) {
                        target.treeList.label.addClass('focus')
                    }
                    that.reveal(target);
                }
            });
            this._data = [];
            this._items = {};
            this._selected = new Set();
            this._topList = $('<ol class="red-ui-treeList-list">').css({
                position:'absolute',
                top:0,
                left:0,
                right:0,
                bottom:0
            }).appendTo(wrapper);

            var topListOptions = {
                addButton: false,
                scrollOnAdd: false,
                height: '100%',
                addItem: function(container,i,item) {
                    that._addSubtree(that._topList,container,item,0);
                }
            };
            if (this.options.header) {
                topListOptions.header = this.options.header;
            }
            if (this.options.rootSortable !== false && !!this.options.sortable) {
                topListOptions.sortable = this.options.sortable;
                topListOptions.connectWith = '.red-ui-treeList-sortable';
                this._topList.addClass('red-ui-treeList-sortable');
            }
            this._topList.editableList(topListOptions)


            if (this.options.data) {
                this.data(this.options.data);
            }
        },
        _getLastDescendant: function(item) {
            // Gets the last visible descendant of the item
            if (!item.children || !item.treeList.container.hasClass("expanded") || item.children.length === 0) {
                return item;
            }
            return this._getLastDescendant(item.children[item.children.length-1]);
        },
        _getPreviousSibling: function(item) {
            var candidates;
            if (!item.parent) {
                candidates = this._data;
            } else {
                candidates = item.parent.children;
            }
            var index = candidates.indexOf(item);
            if (index === 0) {
                return null;
            } else {
                return candidates[index-1];
            }
        },
        _getNextSibling: function(item) {
            var candidates;
            if (!item.parent) {
                candidates = this._data;
            } else {
                candidates = item.parent.children;
            }
            var index = candidates.indexOf(item);
            if (index === candidates.length - 1) {
                return null;
            } else {
                return candidates[index+1];
            }
        },
        _addChildren: function(container,parent,children,depth,onCompleteChildren) {
            var that = this;
            var subtree = $('<ol class="red-ui-treeList-list">').appendTo(container).editableList({
                connectWith: ".red-ui-treeList-sortable",
                sortable: that.options.sortable,
                addButton: false,
                scrollOnAdd: false,
                height: 'auto',
                addItem: function(container,i,item) {
                    that._addSubtree(subtree,container,item,depth+1);
                },
                sortItems: function(data) {
                    var children = [];
                    var reparented = [];
                    data.each(function() {
                        var child = $(this).data('data');
                        children.push(child);
                        var evt = that._fixDepths(parent,child);
                        if (evt) {
                            reparented.push(evt);
                        }
                    })
                    if (Array.isArray(parent.children)) {
                        parent.children = children;
                    }
                    reparented.forEach(function(evt) {
                        that._trigger("changeparent",null,evt);
                    });
                    that._trigger("sort",null,parent);
                },
                filter: parent.treeList.childFilter
            });
            if (!!that.options.sortable) {
                subtree.addClass('red-ui-treeList-sortable');
            }
            var sliceSize = 30;
            var index = 0;
            var addSlice = function() {
                var start = index;
                for (var i=0;i<sliceSize;i++) {
                    index = start+i;
                    if (index === children.length) {
                        setTimeout(function() {
                            if (onCompleteChildren) {
                                onCompleteChildren();
                            }
                        },10);
                        return;
                    }
                    children[index].parent = parent;
                    subtree.editableList('addItem',children[index])
                }
                index++;
                if (index < children.length) {
                    setTimeout(function() {
                        addSlice();
                    },10);
                }
            }
            addSlice();
            subtree.hide()
            return subtree;
        },
        _fixDepths: function(parent,child) {
            // If child has just been moved into parent in the UI
            // this will fix up the internal data structures to match.
            // The calling function must take care of getting child
            // into the parent.children array. The rest is up to us.
            var that = this;
            var reparentedEvent = null;
            if (child.parent !== parent) {
                reparented = true;
                var oldParent = child.parent;
                child.parent = parent;
                reparentedEvent = {
                    item: child,
                    old: oldParent,
                }
            }
            if (child.depth !== parent.depth+1) {
                child.depth = parent.depth+1;
                // var labelPaddingWidth = ((child.gutter ? child.gutter[0].offsetWidth + 2 : 0) + (child.depth * 20));
                var labelPaddingWidth = (((child.gutter&&!child.gutter.hasClass("red-ui-treeList-gutter-float"))?child.gutter.width()+2:0)+(child.depth*20));
                child.treeList.labelPadding.width(labelPaddingWidth+'px');
                if (child.element) {
                    $(child.element).css({
                        width: "calc(100% - "+(labelPaddingWidth+20+(child.icon?20:0))+"px)"
                    })
                }
                // This corrects all child item depths
                if (child.children && Array.isArray(child.children)) {
                    child.children.forEach(function(item) {
                        that._fixDepths(child,item);
                    })
                }
            }
            return reparentedEvent;
        },
        _initItem: function(item,depth) {
            if (item.treeList) {
                return;
            }
            var that = this;
            this._items[item.id] = item;
            item.treeList = {};
            item.depth = depth;
            item.treeList.remove = function(detach) {
                if (item.treeList.parentList) {
                    item.treeList.parentList.editableList('removeItem',item,detach);
                }
                if (item.parent) {
                    var index = item.parent.children.indexOf(item);
                    item.parent.children.splice(index,1)
                    that._trigger("sort",null,item.parent);
                }
                that._selected.delete(item);
                delete item.treeList;
                delete that._items[item.id];
                if(item.depth === 0) {
                    for(var key in that._items) {
                        if (that._items.hasOwnProperty(key)) {
                            var child = that._items[key];
                            if(child.parent && child.parent.id === item.id) {
                                delete that._items[key].treeList;
                                delete that._items[key];
                            }
                        }
                    }
                    that._data = that._data.filter(function(data) { return data.id !== item.id})
                }
            }
            item.treeList.insertChildAt = function(newItem,position,select) {
                newItem.parent = item;
                item.children.splice(position,0,newItem);
                var processChildren = function(parent,i) {
                    that._initItem(i,parent.depth+1)
                    i.parent = parent;
                    if (i.children && typeof i.children !== 'function') {
                        i.children.forEach(function(item) {
                            processChildren(i, item, parent.depth+2)
                        });
                    }
                }
                processChildren(item,newItem);

                if (!item.deferBuild && item.treeList.childList) {
                    item.treeList.childList.editableList('insertItemAt',newItem,position)
                    if (select) {
                        setTimeout(function() {
                            that.select(newItem)
                        },100);
                    }
                    that._trigger("sort",null,item);

                    if (that.activeFilter) {
                        that.filter(that.activeFilter);
                    }
                }
            }
            item.treeList.addChild = function(newItem,select) {
                item.treeList.insertChildAt(newItem,item.children.length,select);
            }
            item.treeList.expand = function(done) {
                if (!item.children) {
                    if (done) { done(false) }
                    return;
                }
                if (!item.treeList.container) {
                    item.expanded = true;
                    if (done) { done(false) }
                    return;
                }
                var container = item.treeList.container;
                if (container.hasClass("expanded")) {
                    if (done) { done(false) }
                    return;
                }

                if (!container.hasClass("built") && (item.deferBuild || typeof item.children === 'function')) {
                    container.addClass('built');
                    var childrenAdded = false;
                    var spinner;
                    var startTime = 0;
                    var started = Date.now();
                    var completeBuild = function(children) {
                        childrenAdded = true;
                        item.treeList.childList = that._addChildren(container,item,children,depth, function() {
                            if (done) { done(true) }
                            that._trigger("childrenloaded",null,item)
                        });
                        var delta = Date.now() - startTime;
                        if (delta < 400) {
                            setTimeout(function() {
                                item.treeList.childList.slideDown('fast');
                                if (spinner) {
                                    spinner.remove();
                                }
                            },400-delta);
                        } else {
                            item.treeList.childList.slideDown('fast');
                            if (spinner) {
                                spinner.remove();
                            }
                        }
                        item.expanded = true;
                    }
                    if (typeof item.children === 'function') {
                        item.children(completeBuild,item);
                    } else {
                        delete item.deferBuild;
                        completeBuild(item.children);
                    }
                    if (!childrenAdded) {
                        startTime = Date.now();
                        spinner = $('<div class="red-ui-treeList-spinner">').css({
                            "background-position": (35+depth*20)+'px 50%'
                        }).appendTo(container);
                    }

                } else {
                    if (that._loadingData || item.children.length > 20) {
                        item.treeList.childList.show();
                    } else {
                        item.treeList.childList.slideDown('fast');
                    }
                    item.expanded = true;
                    if (done) { done(!that._loadingData) }
                }
                container.addClass("expanded");
            }
            item.treeList.collapse = function() {
                if (item.collapsible === false) {
                    return
                }
                if (!item.children) {
                    return;
                }
                item.expanded = false;
                if (item.treeList.container) {
                    if (item.children.length < 20) {
                        item.treeList.childList.slideUp('fast');
                    } else {
                        item.treeList.childList.hide();
                    }
                    item.treeList.container.removeClass("expanded");
                }
            }
            item.treeList.sortChildren = function(sortFunc) {
                if (!item.children) {
                    return;
                }
                item.children.sort(sortFunc);
                if (item.treeList.childList) {
                    // Do a one-off sort of the list, which means calling sort twice:
                    // 1. first with the desired sort function
                    item.treeList.childList.editableList('sort',sortFunc);
                    // 2. and then with null to remove it
                    item.treeList.childList.editableList('sort',null);
                }
            }
            item.treeList.replaceElement = function (element) {
                if (item.element) {
                    if (item.treeList.container) {
                        $(item.element).remove();
                        $(element).appendTo(item.treeList.label);
                        // using the JQuery Object, the gutter width will
                        // be wrong when the element is reattached the second time
                        var labelPaddingWidth = (item.gutter ? item.gutter[0].offsetWidth + 2 : 0) + (item.depth * 20);

                        $(element).css({
                            width: "calc(100% - "+(labelPaddingWidth+20+(item.icon?20:0))+"px)"
                        })
                    }
                    item.element = element;
                }
            }

            if (item.children && typeof item.children !== "function") {
                item.children.forEach(function(i) {
                    that._initItem(i,depth+1);
                })
            }
        },
        _addSubtree: function(parentList, container, item, depth) {
            var that = this;
            this._initItem(item,depth);
            // item.treeList = {};
            // item.treeList.depth = depth;
            item.treeList.container = container;

            item.treeList.parentList = parentList;

            var label = $("<div>",{class:"red-ui-treeList-label"});
            label.appendTo(container);
            item.treeList.label = label;
            if (item.class) {
                label.addClass(item.class);
            }
            if (item.gutter) {
                item.gutter.css({
                    position: 'absolute'
                }).appendTo(label)

            }

            var labelPaddingWidth = ((item.gutter&&!item.gutter.hasClass("red-ui-treeList-gutter-float"))?item.gutter.width()+2:0)+(depth*20);

            item.treeList.labelPadding = $('<span>').css({
                display: "inline-block",
                "flex-shrink": 0,
                width:  labelPaddingWidth+'px'
            }).appendTo(label);

            label.on('mouseover',function(e) { that._trigger('itemmouseover',e,item); })
            label.on('mouseout',function(e) { that._trigger('itemmouseout',e,item); })
            label.on('mouseenter',function(e) { that._trigger('itemmouseenter',e,item); })
            label.on('mouseleave',function(e) { that._trigger('itemmouseleave',e,item); })

            item.treeList.makeLeaf = function(detachChildElements) {
                if (!treeListIcon.children().length) {
                    // Already a leaf
                    return
                }
                if (detachChildElements && item.children) {
                    var detachChildren = function(item) {
                        if (item.children) {
                            item.children.forEach(function(child) {
                                if (child.element) {
                                    child.element.detach();
                                }
                                if (child.gutter) {
                                    child.gutter.detach();
                                }
                                detachChildren(child);
                            });
                        }
                    }
                    detachChildren(item);
                }
                treeListIcon.empty();
                if (!item.deferBuild) {
                    item.treeList.childList.remove();
                    delete item.treeList.childList;
                }
                label.off("click.red-ui-treeList-expand");
                treeListIcon.off("click.red-ui-treeList-expand");
                delete item.children;
                container.removeClass("expanded");
                delete item.expanded;
            }
            item.treeList.makeParent = function(children) {
                if (treeListIcon.children().length) {
                    // Already a parent because we've got the angle-right icon
                    return;
                }
                $('<i class="fa fa-angle-right" />').toggleClass("hide",item.collapsible === false).appendTo(treeListIcon);
                treeListIcon.on("click.red-ui-treeList-expand", function(e) {
                        e.stopPropagation();
                        e.preventDefault();
                        if (container.hasClass("expanded")) {
                            item.treeList.collapse();
                        } else {
                            item.treeList.expand();
                        }
                    });
                // $('<span class="red-ui-treeList-icon"><i class="fa fa-folder-o" /></span>').appendTo(label);
                label.on("click.red-ui-treeList-expand", function(e) {
                    if (container.hasClass("expanded")) {
                        if (item.hasOwnProperty('selected') || label.hasClass("selected")) {
                            item.treeList.collapse();
                        }
                    } else {
                        item.treeList.expand();
                    }
                })
                if (!item.children) {
                    item.children = children||[];
                    item.treeList.childList = that._addChildren(container,item,item.children,depth);
                }
            }

            var treeListIcon = $('<span class="red-ui-treeList-icon"></span>').appendTo(label);
            if (item.children) {
                item.treeList.makeParent();
            }

            if (item.checkbox) {
                var selectWrapper = $('<span class="red-ui-treeList-icon"></span>');
                var cb = $('<input class="red-ui-treeList-checkbox" type="checkbox">').prop('checked',item.selected).appendTo(selectWrapper);
                cb.on('click', function(e) {
                    e.stopPropagation();
                });
                cb.on('change', function(e) {
                    item.selected = this.checked;
                    if (item.selected) {
                        that._selected.add(item);
                    } else {
                        that._selected.delete(item);
                    }
                    label.toggleClass("selected",this.checked);
                    that._trigger("select",e,item);
                })
                if (!item.children) {
                    label.on("click", function(e) {
                        e.stopPropagation();
                        cb.trigger("click");
                        that._topList.find(".focus").removeClass("focus")
                        label.addClass('focus')
                    })
                }
                item.treeList.select = function(v) {
                    if (v !== item.selected) {
                        cb.trigger("click");
                    }
                }
                item.treeList.checkbox = cb;
                selectWrapper.appendTo(label)
            } else if (item.radio) {
                var selectWrapper = $('<span class="red-ui-treeList-icon"></span>');
                var cb = $('<input class="red-ui-treeList-radio" type="radio">').prop('name', item.radio).prop('checked',item.selected).appendTo(selectWrapper);
                cb.on('click', function(e) {
                    e.stopPropagation();
                });
                cb.on('change', function(e) {
                    item.selected = this.checked;
                    that._selected.forEach(function(selectedItem) {
                        if (selectedItem.radio === item.radio) {
                            selectedItem.treeList.label.removeClass("selected");
                            selectedItem.selected = false;
                            that._selected.delete(selectedItem);
                        }
                    })
                    if (item.selected) {
                        that._selected.add(item);
                    } else {
                        that._selected.delete(item);
                    }
                    label.toggleClass("selected",this.checked);
                    that._trigger("select",e,item);
                })
                if (!item.children) {
                    label.on("click", function(e) {
                        e.stopPropagation();
                        cb.trigger("click");
                        that._topList.find(".focus").removeClass("focus")
                        label.addClass('focus')
                    })
                }
                item.treeList.select = function(v) {
                    if (v !== item.selected) {
                        cb.trigger("click");
                    }
                }
                selectWrapper.appendTo(label)
                item.treeList.radio = cb;
            } else {
                label.on("click", function(e) {
                    if (!that.options.multi) {
                        that.clearSelection();
                    }
                    label.addClass("selected");
                    that._selected.add(item);
                    that._topList.find(".focus").removeClass("focus")
                    label.addClass('focus')

                    that._trigger("select",e,item)
                })
                label.on("dblclick", function(e) {
                    that._topList.find(".focus").removeClass("focus")
                    label.addClass('focus')
                    if (!item.children) {
                        that._trigger("confirm",e,item);
                    }
                })
                item.treeList.select = function(v) {
                    if (!that.options.multi) {
                        that.clearSelection();
                    }
                    label.toggleClass("selected",v);
                    if (v) {
                        that._selected.add(item);
                        that._trigger("select",null,item)
                    } else {
                        that._selected.delete(item);
                    }
                    that.reveal(item);
                }
            }
            label.toggleClass("selected",!!item.selected);
            if (item.selected) {
                that._selected.add(item);
            }
            if (item.icon) {
                if (typeof item.icon === "string") {
                    $('<span class="red-ui-treeList-icon"><i class="'+item.icon+'" /></span>').appendTo(label);
                } else {
                    $('<span class="red-ui-treeList-icon">').appendTo(label).append(item.icon);
                }
            }
            if (item.hasOwnProperty('label') || item.hasOwnProperty('sublabel')) {
                if (item.hasOwnProperty('label')) {
                    $('<span class="red-ui-treeList-label-text"></span>').text(item.label).appendTo(label);
                }
                if (item.hasOwnProperty('sublabel')) {
                    $('<span class="red-ui-treeList-sublabel-text"></span>').text(item.sublabel).appendTo(label);
                }

            } else if (item.element) {
                $(item.element).appendTo(label);
                $(item.element).css({
                    width: "calc(100% - "+(labelPaddingWidth+20+(item.icon?20:0))+"px)"
                })
            }
            if (item.children) {
                if (Array.isArray(item.children) && !item.deferBuild) {
                    item.treeList.childList = that._addChildren(container,item,item.children,depth);
                }
                if (item.expanded) {
                    item.treeList.expand();
                }
            }
            // label.appendTo(container);
        },
        empty: function() {
            this._topList.editableList('empty');
        },
        data: function(items) {
            var that = this;
            if (items !== undefined) {
                this._data = items;
                this._items = {};
                this._topList.editableList('empty');
                this._loadingData = true;
                for (var i=0; i<items.length;i++) {
                    this._topList.editableList('addItem',items[i]);
                }
                setTimeout(function() {
                    delete that._loadingData;
                },200);
                this._trigger("select")

            } else {
                return this._data;
            }
        },
        show: function(item, done) {
            if (typeof item === "string") {
                item = this._items[item]
            }
            if (!item) {
                return;
            }
            var that = this;
            var stack = [];
            var i = item;
            while(i) {
                stack.unshift(i);
                i = i.parent;
            }
            var isOpening = false;
            var handleStack = function(opening) {
                isOpening = isOpening ||opening
                var item = stack.shift();
                if (stack.length === 0) {
                    setTimeout(function() {
                        that.reveal(item);
                        if (done) { done(); }
                    },isOpening?200:0);
                } else {
                    item.treeList.expand(handleStack)
                }
            }
            handleStack();
        },
        reveal: function(item) {
            if (typeof item === "string") {
                item = this._items[item]
            }
            if (!item) {
                return;
            }
            var listOffset = this._topList.offset().top;
            var itemOffset = item.treeList.label.offset().top;
            var scrollTop = this._topList.parent().scrollTop();
            itemOffset -= listOffset+scrollTop;
            var treeHeight = this._topList.parent().height();
            var itemHeight = item.treeList.label.outerHeight();
            if (itemOffset < itemHeight/2) {
                this._topList.parent().scrollTop(scrollTop+itemOffset-itemHeight/2-itemHeight)
            } else if (itemOffset+itemHeight > treeHeight) {
                this._topList.parent().scrollTop(scrollTop+((itemOffset+2.5*itemHeight)-treeHeight));
            }
        },
        select: function(item, triggerEvent, deselectExisting) {
            var that = this;
            if (!this.options.multi && deselectExisting !== false) {
                this.clearSelection();
            }
            if (Array.isArray(item)) {
                item.forEach(function(i) {
                    that.select(i,triggerEvent,false);
                })
                return;
            }
            if (typeof item === "string") {
                item = this._items[item]
            }
            if (!item) {
                return;
            }
            // this.show(item.id);
            item.selected = true;
            this._selected.add(item);

            if (item.treeList.label) {
                item.treeList.label.addClass("selected");
            }

            that._topList.find(".focus").removeClass("focus");
            
            if (item.treeList.label) {
                item.treeList.label.addClass("focus");
            }

            if (triggerEvent !== false) {
                this._trigger("select",null,item)
            }
        },
        clearSelection: function() {
            this._selected.forEach(function(item) {
                item.selected = false;
                if (item.treeList.checkbox) {
                    item.treeList.checkbox.prop('checked',false)
                }
                if (item.treeList.label) {
                    item.treeList.label.removeClass("selected")
                }
            });
            this._selected.clear();
        },
        selected: function() {
            var selected = [];
            this._selected.forEach(function(item) {
                selected.push(item);
            })
            if (this.options.multi) {
                return selected;
            }
            if (selected.length) {
                return selected[0]
            } else {
                // TODO: This may be a bug.. it causes the call to return itself
                // not undefined.
                return undefined;
            }
        },
        filter: function(filterFunc) {
            this.activeFilter = filterFunc;
            var totalCount = 0;
            var filter = function(item) {
                var matchCount = 0;
                if (filterFunc && filterFunc(item)) {
                    matchCount++;
                    totalCount++;
                }
                var childCount = 0;
                if (item.children && typeof item.children !== "function") {
                    if (item.treeList.childList) {
                        childCount = item.treeList.childList.editableList('filter', filter);
                    } else {
                        item.treeList.childFilter = filter;
                        if (filterFunc) {
                            item.children.forEach(function(i) {
                                if (filter(i)) {
                                    childCount++;
                                }
                            })

                        }
                    }
                    matchCount += childCount;
                    if (filterFunc && childCount > 0) {
                        setTimeout(function() {
                            item.treeList.expand();
                        },10);
                    }
                }
                if (!filterFunc) {
                    totalCount++;
                    return true
                }
                return matchCount > 0
            }
            this._topList.editableList('filter', filter);
            return totalCount;
        },
        get: function(id) {
            return this._items[id] || null;
        }
    });

})(jQuery);
