define(
    [
        'scripts/knockout-3.3.0','scripts/ACTComponent.js',
        'jqx/jqxbuttons', 'jqx/jqxscrollbar',
        'jqx/jqxpanel', 'jqx/jqxtree',
        'jqx/jqxdragdrop','scripts/CallWebObject.js',
        "scripts/ACTUtils",'jqx/jqxtooltip'
    ],
    function(ko, ACTComponent, jqxButtons, jqxScrollbar, jqxPanel, jqxtree, jqxdragdrop, InvokeCallback, ACTUtils) {

        function TreeComponentViewModel(params, element) {
            var self = this;

            this.base         = new ACTComponent($(element), params);
            this.element      = element;
            this.params       = params;
            this.ItemSelected = params.Model.ItemSelected;
            this.DragEnd      = params.Model.DragEnd;
            this.selectedNode = null;

            $(element).css("overflow", "hidden");

            var $tree = $(element).find('#jqxTree');
            var $menu = $(element).find('#jqxMenu');

            this.base.addCallback1(this, "onSelect", "ItemSelected");
            this.base.addCallbackX(this, "onDragEnd", "DragEnd");

            this.base.onRefresh = function(obj) {
                if (obj.TriggerSelection != null) {
                    this.selectedNode = obj.TriggerSelection;
                    this.applySelectionWithId(obj.TriggerSelection);
                    return;
                } else if (obj.TriggerContextMenuClick != null) {
                    var $elem = $tree.find("#" + obj.TriggerContextMenuClick).find(".jqx-tree-item").first();
                    $elem.trigger("contextmenu");
                    return;
                }

                this.title = obj.Title;
                if (this.title != ""){
                    this.computeTitlePosition();
                }
                this.hideRootNode = obj.HideRootNode;
                if(this.hideRootNode){
                    if (obj.RootNode == null)
                        this.sourceTree = []
                    else
                        this.sourceTree = obj.RootNode.items;
                } else {
                    this.sourceTree = [obj.RootNode];
                }

                $tree.jqxTree({
                    'source' : this.sourceTree,
                    width: '100%',
                    height: '100%',
                    allowDrag: obj.AllowDrag,
                    allowDrop: obj.AllowDrop,
                    toggleMode: obj.ToggleMode,
                });

                var $nodesElements = $tree.find("li").find(".jqx-tree-item");

                // selection
                this.selectedNode = obj.SelectedNode;
                this.applySelection(this.selectedNode);

                // tree node tooltip
                ACTUtils.CreateTooltip($tree, "", false, {disabled: true});
                var ShowTooltip = function($target, tooltipText) {
                    CloseTooltip();
                    if (!ACTUtils.StringIsNullOrEmpty(tooltipText)) {
                        $tree.jqxTooltip({content: tooltipText, disabled: false});
                        $tree.jqxTooltip("open", $target.offset().left + $target.width() + 10, $target.offset().top);
                    }
                    return false;
                };

                var CloseTooltip = function() {
                    $tree.jqxTooltip("close");
                    $tree.jqxTooltip({disabled: true});
                    return false;
                }

                $nodesElements.on("mouseenter", function(e) {
                    var $target = $(e.target);
                    var $liParent = $target.parents("li");
                    var item = $tree.jqxTree('getItem', $liParent[0]);
                    var tooltipText = jQuery.data($liParent[0], "node").tooltip;
                    ShowTooltip($target, tooltipText);
                });
                $nodesElements.on("mouseleave", CloseTooltip);

                // context menu
                this.ContextMenuClick = obj.ContextMenuClick;
                this.base.addCallbackX(this, "contextMenuClick", "ContextMenuClick");
                function SetDataOnTreeElement(node)
                {
                    if (node == null) return;
                    var $node = $(element).find("#" + node.id);
                    jQuery.data($node[0], "node", node);

                    for (var i = 0; i < node.classes.length; i++)
                    {
                        $node.addClass(node.classes[i]);
                    }

                    for (var i = 0; i < node.items.length; i++)
                    {
                        SetDataOnTreeElement(node.items[i]);
                    }
                }

                for (var i=0; i<this.sourceTree.length; i++)
                {
                    SetDataOnTreeElement(this.sourceTree[i]);
                }

                $nodesElements.on("contextmenu", function(e) {  // simular to on("rightClick",... but remove the usual context menu
                    $tree.jqxTooltip("close");

                    var $liParent = $(e.target).parents("li");
                    var item = $tree.jqxTree('getItem', $liParent[0]);
                    var node = jQuery.data($liParent[0], "node");

                    var scrollTop = $(element).scrollTop();
                    var scrollLeft = $(element).scrollLeft();
                    var positionX = parseInt(e.clientX) + 5 + scrollLeft;
                    var positionY = parseInt(e.clientY) + 5 + scrollTop;

                    params.baseModel.onTriggerAction("TriggerContextMenuClick", [node.id]);
                    this.contextMenuClick(node.id, positionX, positionY);

                    return false;
                }.bind(this));

                if (obj.Expanded) $tree.jqxTree('expandAll');

                // drag and drop
                $tree.jqxTree('dragEnd', function (dragItem, dropItem, args, dropPosition, tree){
                    if(dropItem) { //since the current dragItem still exists after the refresh, this method will be called again but with a dropItem == null.
                        if (!dropItem.id || !dropPosition) {
                            var $treeDropables = $("._ACT_TREE_DROPPABLE_"); // set by ACTComponent
                            var ev = dropItem.originalEvent;
                            var x = ev.pageX;
                            var y = ev.pageY;

                            for (var i = 0; i < $treeDropables.length; i++)
                            {
                                var $treeDropable = $($treeDropables[i]);
                                if (ACTUtils.PositionIsInElement($treeDropable, x, y)) {
                                    var dropableBase = ko.dataFor($treeDropable.children()[0]).base;
                                    dropableBase.Model.DragAndDropInfo.treenodedrop(dragItem.id);
                                }
                            }
                        } else {
                            this.onDragEnd(dragItem.id, dropItem.id, dropPosition);
                        }
                    }
                    return false;
                }.bind(this));
            }.bind(this);

            this.base.onMessage = function(name, obj) {
                if (name == "SelectThisNode") {
                    this.selectedNode = obj;
                    this.applySelection(obj);
                } else if (name == "RefreshNode") {
                    var node = obj;
                    var nodeId = node.id;
                    var nodeElement = $tree.find("#" + nodeId)[0];
                    $tree.jqxTree('updateItem', nodeElement, {label: node.label, icon: node.icon});
                }
            }.bind(this);

            this.applySelection = function(node) {
                if (node == null) {
                    $tree.jqxTree("selectItem", null);
                    return;
                }

                var nodeId = node.id;
                this.applySelectionWithId(nodeId);
            }

            this.applySelectionWithId = function(nodeId) {
                var nodeElementToSelect = $tree.find("#" + nodeId)[0];
                if (nodeElementToSelect == null) {
                    console.warn("invalid selection");
                    return;
                }

                // expand parents
                var nodeItemToSelect = $tree.jqxTree("getItem", nodeElementToSelect);
                var parentItem = $tree.jqxTree("getItem", nodeItemToSelect.parentElement);
                while (parentItem != null && !parentItem.isExpanded) {
                    $tree.jqxTree("expandItem", parentItem);
                    parentItem = $tree.jqxTree("getItem", parentItem.parentElement);
                }
                $tree.jqxTree("selectItem", nodeElementToSelect);
                $tree.jqxTree("ensureVisible", nodeElementToSelect);
            }

            // selection
            // setTimeout cause blur of focused element never call if we hide the element with the item selection
            $tree.on("select", function(event) {
                /*
                 * remove the focus cause sometimes we need that to save the state of
                 * a component and select an item in the tree don't remove the focus.
                 * Since we can change this same element when selecting an item we force the lost of focus.
                 */
                $(document.activeElement).blur();

                self.select();
            });
            self.select = function() {
                var item = $tree.jqxTree('getItem', args.element);
                self.onSelect(item.id);
                params.baseModel.onTriggerAction("TriggerSelect", [item.id]);
                if (self.params.Model.AllowSelection === false) {
                    setTimeout(function(){ $tree.jqxTree('selectItem', null); }, 0);
                }
            }

            this.computeTitlePosition = function() {
                if(this.title){
                    var $title = $(element).find('#tree-title');
                    $title.text(this.title);
                    var $treeParent = $(element).find('.tree-parent');
                    var newHeight = $(element).height() - $title.height();
                    $(element).find('.tree-parent').css('height', newHeight);
                }
            };

            // standart drop (not node drop)
            this.base.onDrop = function(event, ui) {
                var $target = $(event.toElement);

                var $li = null;
                if ($target[0].nodeName == "LI") {
                    $li = $target
                } else {
                    $li = $($target.parents("li.jqx-tree-item-li")[0]);
                }

                if ($li.length == 0) {
                    return null;
                }

                var node = jQuery.data($li[0], "node");
                return node.id;
            };

            params.baseModel.resizeFunctions[params.baseModel.resizeFunctions.length] = function(height,width) {
                this.computeTitlePosition();
                $tree.jqxTree('refresh');
                //$(".tree-container").height(height-15);
            }.bind(this);

            params.baseModel.showFunctions.push(function() {
                if (this.title)
                    this.computeTitlePosition();
                // hiding the component remove the selection
                this.applySelection(this.selectedNode);
            }.bind(this));

            this.base.initialize();
            params.ready(true);
        }

        return {
            createViewModel: function(params, componentInfo) {
                return new TreeComponentViewModel(params, componentInfo.element);
            }
        };
    }
);
