/// class PD.Web.UI.Controls.TreeView
// using PD.Web.UI.Controls.Control

PD.Web.UI.Controls.TreeView =
PD.Web.UI.Controls.Control.extend({

    //===================================
    // FIELDS
    //==================================
    
    _tree: null,
    _dataSource: null,
    _modules: null,
    _deferred: null,
    _supressChangeEvent: false,

    //===================================
    // CONSTRUCTOR
    //==================================

    init: function (options) {
        this._modules = new PD.Web.UI.Controls.GridModuleCollection();
        this._deferred = new PD.BCL.List();
        this._super($.extend(true, this.getDefaultOptions(), options));
    },

    //===================================
    // PROPERTIES
    //==================================

    get_options: function () {
        return this._options;
    },

    get_kendo: function () {
        return this._tree;
    },

    get_dataSource: function () {
        return this._dataSource;
    },

    get_deferred: function () {
        return this._deferred;
    },

    //===================================
    // EVENT HANDLERS
    //===================================

    onActivate: function (e) {
        this._initDataSource();
        this._initTree();
        this.get_node().attr("MISC", "explorer-model");
    },

    onComplete: function () {
    
        var readyCallbackFn = this.getOption("ready", function () { });

        $.proxy(readyCallbackFn, this)();

    },

    _onDataBound: function (e) {

        var that = this,
            source = this.get_deferred(),
            clones = this.get_deferred().clone();

        if (source.count() > 0) {
            clones.each(function (index, deferred) {
                source.remove(deferred);
                deferred();
            });
            source.clear();
        }

    },

    //===================================
    // PUBLIC METHODS
    //===================================

    modules: function (id) {
        if (id !== undefined) {
            return this._modules.get(id);
        }
        return this._modules;
    },

    getSelection: function () {
        return this.modules("selection") ? this.modules("selection").getSelectedItem() : null;
    },

    getSelections: function () {

        var result = null,
            pk = this.get_options()["pk"];

        if (this.modules("selection")) {
            result = this.modules("selection").getSelections();
        }

        return result || new PD.Web.UI.Controls.GridSelection(pk);

    },

    getOption: function (path, defaultValue) {

        var result = undefined,
            base = this.get_options(),
            items = new PD.BCL.List(path.split(".")),
            lastIndex = items.count() - 1;

        items.each(function (index, item) {
            if (base[item] !== undefined) {
                base = base[item]
                if (index === lastIndex) {
                    result = base;
                }
            }
            else {
                return false;
            }
        });

        if (result === undefined) {
            result = defaultValue;
        }

        return result;

    },

    getDefaultOptions: function () {
        return {
            dragAndDrop: true,
            template: $.proxy(this.nodeTemplate, this),
            dataBound: $.proxy(this._onDataBound, this)
        };
    },

    dataItem: function ($node) {
        return this.get_kendo().dataItem($node);
    },

    removeNode: function (id) {
    
        var item = this.get_dataSource().get(id),
            fallbackFn = this._fallback(id);

        if (item) {
            this.get_kendo().remove("LI[data-uid=" + item.uid + "]");
            this.get_dataSource().remove(id);
            fallbackFn();
        }

    },

    addNode: function (node, parentId) {
    
        var that = this,
            parent = null,
            $parentNode = null,
            $node = null,
            requiresAppend = true,
            selectNodeFn = null;

        if (parentId != null) {
            parent = this.get_dataSource().get(parentId);
            if (parent != null) {
                $parentNode = this.get_node().find("[data-uid=" + parent.uid + "]");
                requiresAppend = parent.loaded();
            }
        }

        selectNodeFn = function (attempt) {
            attempt = attempt || 0;
            attempt += 1;
            if (parent.loaded()) {
                setTimeout(function () {
                    that._tree.expand($parentNode);
                    that.select(node.id, true);
                }, 50);
            }
            else if (attempt < 20) {
                setTimeout(function () {
                    selectNodeFn(attempt);
                }, 100);
            }
        };

        // do not append if the parent node's children are populated remotely
        // and the parent node's children collection has not yet been loaded.
        if (requiresAppend) {
            $node = this.get_kendo().append(node, $parentNode);
            that.select(node.id, true);
        }
        else {
            parent.hasChildren = true;
            this.get_deferred().add(function () {
                selectNodeFn();
            });
            parent.load();
        }

    },

    select: function (id, triggerEvent) {

        var item = null,
            $node = null;

        if (id == undefined) {
            $node = this.get_kendo().select();
            return this.get_kendo().dataItem($node);
        }

        item = this.get_kendo().dataSource.get(id);

        if (item != null) {
            $node = this.get_node().find("[data-uid=" + item.uid + "]");
        }

        if ($node != null && $node.length > 0) {

            this._supressChangeEvent = triggerEvent != null ? !triggerEvent : false;
            this.get_kendo().select($node);
            
        }

    },

    reload: function (id) {
    
        var item = null,
            $node = null,
            children = null,
            child = null;

        // when calling reload mutliple times in succession exceptions may be thrown when calling get on dataSource
        // could be fixed by allowing a callback function to be passed to this method and invoking the callback post
        // a call to load() at the end of this method and post execution of the "databound" event of the treeview control.
        try {

            if (id == undefined) {
                $node = this.get_kendo().select();
                item = this.get_kendo().dataItem($node);
            }
            else {
                item = this.get_kendo().dataSource.get(id);
            }

        }
        catch (ex) {

        }

        if (item != null) {
            
            $node = this.get_kendo().findByUid(item.uid);

            if (item.hasChildren) {

                children = item.children.data();

                if (children != null) { // even when the hasChildren property is true, there are some cases where the `children` array is null.

                    for (var i = 0, max = children.length; i < max; i++) {
                        child = this.get_kendo().findByUid(children[0].uid);
                        this.get_kendo().remove(child);
                    }

                }

            }

            item.hasChildren = true;
            item.loaded(false);
            item.load();

            this.get_kendo().expand($node);

        }

    },

    update: function (id, data) {
    
        var item = this._updateDataItem(id, data),
            $node = null,
            $nodeKin;

        if (item) {

            $node = this.get_node().find("LI[data-uid=" + item.uid + "]");

            if ($node.length > 0) {
                $nodeKin = $node.children("DIV").find(".k-in");
                $nodeKin.empty();
                $nodeKin.html(this.nodeTemplate({
                    item: item
                }));
            }

        }

    },

    expandPath: function (ids, callback) {

        var that = this,
            last = _.last(ids);

        // must select node here first for cases where 
        // the path has already been expanded 
        // to ensure the node is selected
        this.select(last, false);

        this.get_kendo().expandPath(ids, function () {
            if (_.isFunction(callback)) {
                callback();
            }
            that.select(last, false);
        });

    },

    isSelected: function (id) {

        var $node = this.get_kendo().select(),
            item = null,
            result = false;

        if ($node.length > 0) {
            item = this.dataItem($node);
            if (item) {
                result = id === item.id;
            }
        }

        return result;

    },

    clearSelection: function () {
        this.get_kendo().select($());
    },

    //===================================
    // PROTECTED METHODS
    //===================================

    template: function (e) {
        return "<div class='-pd-treeview' style='padding: 5px;'></div>";
    },

    nodeTemplate: function (e) {

        var item = e.item,
            icon = [
                "<div style='position:absolute;top:0;left:0;width:24px;height:24px;padding:2px;box-sizing:border-box;'>",
                    "<img src='" + PD.Path.resolve("~/application/icons/16x16/" + item.icon) + "' />",
                "</div>"
            ],
            result = [
                "<div style='position:relative;padding:3px;padding-left:" + (item.icon ? "24px" : "3px") + ";white-space:nowrap;vertical-align:top;display:inline-block;" + (item.isHidden ? "opacity:.4;" : "") + "'>",
                    item.icon ? icon.join("") : "",
                    "<span style='padding:3px;box-sizing:border-box;" + (item.isTab ? "text-decoration:underline;" : "") + "'>",
                        item.name,
                    "</span>",
                "</div>"
            ];

        return result.join("");

    },
    
    addDefaultModules: function (modules) {
        var NS = PD.Web.UI.Controls;
    },

    addModules: function (modules) {

        var NS = PD.Web.UI.Controls;

        modules.add(new NS.TreeViewDragAndDropModule());

    },

    addOptionModules: function (modules) {

        var optionModules = new PD.BCL.List(this.get_options()["modules"]);

        optionModules.each(function (index, name) {
            var type = PD.Type.getType(name);
            if (type != null) {
                modules.add(new type());
            }
        });

    },

    //===================================
    // PRIVATE METHODS
    //===================================
    
    _initDataSource: function () {
        this._dataSource = new PD.Web.UI.Controls.TreeViewDataSource({
            tree: this
        });
    },

    _initTree: function () {

        if (this.get_kendo() != null) {
            this.get_kendo().destroy();
        };

        this.get_node().empty();
        
        this._tree = this.get_node()
            .kendoTreeView(this.get_options())
            .data("kendoTreeView");
        
        this.get_kendo().setDataSource(this._dataSource.get_instance());

        this._initModules();
        
        //this.get_kendo().setOptions(this.get_options());

        this._startModules();

    },

    _initModules: function () {

        var that = this;

        this.modules().each(function (index, module) {
            module.stop();
        });

        this.modules().clear();

        if (this.modules().count() === 0) {
            this.addDefaultModules(this.modules());
            this.addModules(this.modules());
            this.addOptionModules(this.modules());
        }

        this.modules().each(function (index, module) {
            module.activate(that);
            var options = module.getOptionSettings();
            if (options != null) {
                //that.get_kendo().setOptions(options);
            }
        });

    },

    _startModules: function () {

        this.modules().each(function (index, module) {
            if (module.meetsLoadConditions()) {
                module.start();
            }
        });

        this.modules().each(function (index, module) {
            if (module.meetsLoadConditions()) {
                module.ready();
            }
        });

    },

    _needsFallback: function (id) {
    
        var item = this.get_dataSource().get(id),
            $1node = $();

        if (item != null) {
            $1node = this.get_kendo().element.find("LI[data-uid=" + item.uid + "]");
        }

        return $1node.find(".k-in").hasClass("k-state-selected");

    },

    _fallback: function (id) {

        var that = this,
            item = this.get_dataSource().get(id),
            needsFallback = this._needsFallback(id),
            $1node = $(),
            $fallbackNode = null,
            result = function () { };

        if (item != null) {
            $1node = this.get_kendo().element.find("LI[data-uid=" + item.uid + "]");
        }

        if (needsFallback && $1node.length > 0) {

            $fallbackNode = $1node.next();

            if ($fallbackNode.length === 0) {
                $fallbackNode = $1node.prev();
            }

            if ($fallbackNode.length === 0) {
                $fallbackNode = $1node.parent().closest("LI[data-uid]");
            }

            if ($fallbackNode.length > 0) {
                result = function () {
                    that.get_kendo().select($fallbackNode);
                    that.get_kendo().trigger("select", {
                        node: $fallbackNode
                    });

                };
            }

        }

        return result;

    },

    _updateDataItem: function (id, data) {

        var item = this.get_dataSource().get(id);

        if (item) {
            for (key in data) {
                if (item.hasOwnProperty(key)) {
                    item.set(key, data[key]);
                }
            }
        }

        return item;

    },

    _ensureSelectionsAreCleared: function () {

        var selectionsCss = this.get_kendo().element.find(".k-state-selected"),
            selectionsAttr = this.get_kendo().element.find(".k-item[aria-selected=true]");

        selectionsCss.removeClass("k-state-selected");
        selectionsAttr.prop("ariaSelected", false);
        //selectionsAttr.removeAttr("aria-selected");

    }

});