/*
 * JBoss, Home of Professional Open Source
 * Copyright ${year}, Red Hat, Inc. and individual contributors
 * by the @authors tag. See the copyright.txt in the distribution for a
 * full listing of individual contributors.
 *
 * This is free software; you can redistribute it and/or modify it
 * under the terms of the GNU Lesser General Public License as
 * published by the Free Software Foundation; either version 2.1 of
 * the License, or (at your option) any later version.
 *
 * This software is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this software; if not, write to the Free
 * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
 * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
 */
(function($, richfaces) {

    var NEW_NODE_TOGGLE_STATE = "__NEW_NODE_TOGGLE_STATE";

    var TRIGGER_NODE_AJAX_UPDATE = "__TRIGGER_NODE_AJAX_UPDATE";

    var SELECTION_STATE = "__SELECTION_STATE";

    var TREE_CLASSES = ["rf-tr-nd-colps", "rf-tr-nd-exp"];

    var TREE_HANDLE_CLASSES = ["rf-trn-hnd-colps", "rf-trn-hnd-exp"];

    var TREE_ICON_CLASSES = ["rf-trn-ico-colps", "rf-trn-ico-exp"];

    richfaces.ui = richfaces.ui || {};

    richfaces.ui.TreeNode = richfaces.BaseComponent.extendClass({

            name: "TreeNode",

            init: function (id, commonOptions) {
                $superTreeNode.constructor.call(this, id);
                this.__rootElt = $(this.attachToDom());

                this.__children = new Array();

                this.__initializeChildren(commonOptions);

                var handlers = (commonOptions.clientEventHandlers || {})[this.getId().substring(commonOptions.treeId.length)] || {};

                if (handlers.bth) {
                    richfaces.Event.bind(this.__rootElt, "beforetoggle", new Function("event", handlers.bth));
                }

                if (handlers.th) {
                    richfaces.Event.bind(this.__rootElt, "toggle", new Function("event", handlers.th));
                }

                this.__addLastNodeClass();
            },

            destroy: function() {

                if (this.parent) {
                    this.parent.removeChild(this);
                    this.parent = null;
                }

                this.__clientToggleStateInput = null;

                this.__clearChildren();

                this.__rootElt = null;

                $superTreeNode.destroy.call(this);
            },

            __initializeChildren: function(commonOptions) {
                var _this = this;
                this.__rootElt.children(".rf-tr-nd").each(function() {
                    _this.addChild(new richfaces.ui.TreeNode(this, commonOptions));
                });
            },

            __addLastNodeClass: function() {
                if (this.__rootElt.next("div").length == 0) {
                    this.__rootElt.addClass("rf-tr-nd-last");
                }
            },

            __getNodeContainer: function() {
                return this.__rootElt.find(" > .rf-trn:first");
            },

            __getHandle: function() {
                return this.__getNodeContainer().find(" > .rf-trn-hnd:first");
            },

            __getContent: function() {
                return this.__getNodeContainer().find(" > .rf-trn-cnt:first");
            },

            __getIcons: function() {
                return this.__getContent().find(" > .rf-trn-ico");
            },

            getParent: function() {
                return this.__parent;
            },

            setParent: function(newParent) {
                this.__parent = newParent;
            },

            addChild: function(child, idx) {
                var start;
                if (typeof idx != 'undefined') {
                    start = idx;
                } else {
                    start = this.__children.length;
                }

                this.__children.splice(start, 0, child);
                child.setParent(this);
            },

            removeChild: function(child) {
                if (this.__children.length) {
                    var idx = this.__children.indexOf(child);
                    if (idx != -1) {
                        var removedChildren = this.__children.splice(idx, 1);
                        if (removedChildren) {
                            for (var i = 0; i < removedChildren.length; i++) {
                                removedChildren[i].setParent(undefined);
                            }
                        }
                    }
                }
            },

            __clearChildren: function() {
                for (var i = 0; i < this.__children.length; i++) {
                    this.__children[i].setParent(undefined);
                }

                this.__children = new Array();
            },

            isExpanded: function() {
                return !this.isLeaf() && this.__rootElt.hasClass("rf-tr-nd-exp");
            },

            isCollapsed: function() {
                return !this.isLeaf() && this.__rootElt.hasClass("rf-tr-nd-colps");
            },

            isLeaf: function() {
                return this.__rootElt.hasClass("rf-tr-nd-lf");
            },

            __canBeToggled: function() {
                return !this.isLeaf() && !this.__rootElt.hasClass("rf-tr-nd-exp-nc") && !this.__loading;
            },

            toggle: function() {
                if (!this.__canBeToggled()) {
                    return;
                }

                if (this.isCollapsed()) {
                    this.expand();
                } else {
                    this.collapse();
                }
            },

            __updateClientToggleStateInput: function(newState) {
                if (!this.__clientToggleStateInput) {
                    this.__clientToggleStateInput = $("<input type='hidden' />").appendTo(this.__rootElt)
                        .attr({name: this.getId() + NEW_NODE_TOGGLE_STATE});
                }

                this.__clientToggleStateInput.val(newState.toString());

            },

            __fireBeforeToggleEvent: function() {
                return richfaces.Event.callHandler(this.__rootElt, "beforetoggle");
            },

            __fireToggleEvent: function() {
                richfaces.Event.callHandler(this.__rootElt, "toggle");
            },

            __makeLoading: function() {
                this.__loading = true;
                this.__getNodeContainer().addClass("rf-trn-ldn");
            },

            __resetLoading: function() {
                this.__loading = false;
                this.__getNodeContainer().removeClass("rf-trn-ldn");
            },

            __changeToggleState: function(newState) {
                if (!this.isLeaf()) {
                    if (newState ^ this.isExpanded()) {

                        if (this.__fireBeforeToggleEvent() === false) {
                            return;
                        }

                        var tree = this.getTree();

                        switch (tree.getToggleType()) {
                            case 'client':
                                this.__rootElt.addClass(TREE_CLASSES[newState ? 1 : 0]).removeClass(TREE_CLASSES[!newState ? 1 : 0]);
                                this.__getHandle().addClass(TREE_HANDLE_CLASSES[newState ? 1 : 0]).removeClass(TREE_HANDLE_CLASSES[!newState ? 1 : 0]);

                                var icons = this.__getIcons();
                                if (icons.length == 1) {
                                    icons.addClass(TREE_ICON_CLASSES[newState ? 1 : 0]).removeClass(TREE_ICON_CLASSES[!newState ? 1 : 0]);
                                }

                                this.__updateClientToggleStateInput(newState);
                                this.__fireToggleEvent();
                                break;

                            case 'ajax':
                            case 'server':
                                //TODO - event?
                                tree.__sendToggleRequest(null, this, newState);
                                break;
                        }
                    }
                }
            },

            collapse: function() {
                this.__changeToggleState(false);
            },

            expand: function() {
                this.__changeToggleState(true);
            },

            __setSelected: function(value) {
                var content = this.__getContent();
                if (value) {
                    content.addClass("rf-trn-sel");
                } else {
                    content.removeClass("rf-trn-sel");
                }

                this.__selected = value;
            },

            isSelected: function() {
                return this.__selected;
            },

            getTree: function() {
                return this.getParent().getTree();
            },

            getId: function() {
                return this.__rootElt.attr('id');
            }

        });

    // define super class link for TreeNode
    var $superTreeNode = richfaces.ui.TreeNode.$super;

    richfaces.ui.TreeNode.initNodeByAjax = function(nodeId, commonOptions) {
        var node = $(document.getElementById(nodeId));

        var opts = commonOptions || {};

        var parent = node.parent(".rf-tr-nd, .rf-tr");

        var idx = node.prevAll(".rf-tr-nd").length;

        var parentNode = richfaces.$(parent[0]);
        opts.treeId = parentNode.getTree().getId();

        var newChild = new richfaces.ui.TreeNode(node[0], opts);
        parentNode.addChild(newChild, idx);

        var tree = parentNode.getTree();

        if (tree.getSelection().contains(newChild.getId())) {
            newChild.__setSelected(true);
        }
    };

    richfaces.ui.TreeNode.emitToggleEvent = function(nodeId) {
        var node = document.getElementById(nodeId);
        if (!node) {
            return;
        }

        richfaces.$(node).__fireToggleEvent();
    };

    var findTree = function(elt) {
        return richfaces.$($(elt).closest(".rf-tr"));
    };

    var findTreeNode = function(elt) {
        return richfaces.$($(elt).closest(".rf-tr-nd"));
    };

    var isEventForAnotherTree = function(tree, elt) {
        return tree != findTree(elt);
    };

    richfaces.ui.Tree = richfaces.ui.TreeNode.extendClass({

            name: "Tree",

            init: function (id, options) {
                this.__treeRootElt = $(richfaces.getDomElement(id));

                var commonOptions = {};
                commonOptions.clientEventHandlers = options.clientEventHandlers || {};
                commonOptions.treeId = id;

                $superTree.constructor.call(this, this.__treeRootElt, commonOptions);

                this.__toggleType = options.toggleType || 'ajax';
                this.__selectionType = options.selectionType || 'client';

                if (options.ajaxSubmitFunction) {
                    this.__ajaxSubmitFunction = new Function("event", "source", "params", "complete", options.ajaxSubmitFunction);
                }

                if (options.onbeforeselectionchange) {
                    richfaces.Event.bind(this.__treeRootElt, "beforeselectionchange", new Function("event", options.onbeforeselectionchange));
                }

                if (options.onselectionchange) {
                    richfaces.Event.bind(this.__treeRootElt, "selectionchange", new Function("event", options.onselectionchange));
                }

                this.__toggleNodeEvent = options.toggleNodeEvent;
                if (this.__toggleNodeEvent) {
                    this.__treeRootElt.delegate(".rf-trn", this.__toggleNodeEvent, this, this.__nodeToggleActivated);
                }
                if (!this.__toggleNodeEvent || this.__toggleNodeEvent != 'click') {
                    this.__treeRootElt.delegate(".rf-trn-hnd", "click", this, this.__nodeToggleActivated);
                }

                this.__treeRootElt.delegate(".rf-trn-cnt", "mousedown", this, this.__nodeSelectionActivated);

                this.__findSelectionInput();
                this.__selection = new richfaces.ui.TreeNodeSet(this.__selectionInput.val());

                $(document).ready($.proxy(this.__updateSelectionFromInput, this));
            },

            __findSelectionInput: function () {
                this.__selectionInput = $(" > .rf-tr-sel-inp", this.__treeRootElt);
            },

            __addLastNodeClass: function() {
                //stub function overriding parent class method
            },

            destroy: function() {
                if (this.__toggleNodeEvent) {
                    this.__treeRootElt.undelegate(".rf-trn", this.__toggleNodeEvent, this, this.__nodeToggleActivated);
                }
                if (!this.__toggleNodeEvent || this.__toggleNodeEvent != 'click') {
                    this.__treeRootElt.undelegate(".rf-trn-hnd", "click", this, this.__nodeToggleActivated);
                }

                this.__treeRootElt.undelegate(".rf-trn-cnt", "mousedown", this.__nodeSelectionActivated);
                this.__treeRootElt = null;

                this.__selectionInput = null;
                this.__ajaxSubmitFunction = null;
                $superTree.destroy.call(this);
            },

            __nodeToggleActivated: function(event) {
                var theTree = event.data;
                if (isEventForAnotherTree(theTree, this)) {
                    return;
                }

                var treeNode = findTreeNode(this);
                treeNode.toggle();
            },

            __nodeSelectionActivated: function(event) {
                var theTree = event.data;
                if (isEventForAnotherTree(theTree, this)) {
                    return;
                }

                var treeNode = findTreeNode(this);

                if (event.ctrlKey) {
                    theTree.__toggleSelection(treeNode);
                } else {
                    theTree.__addToSelection(treeNode);
                }
            },

            __sendToggleRequest: function(event, toggleSource, newNodeState) {
                var toggleSourceId = toggleSource.getId();

                var clientParams = {};
                clientParams[toggleSourceId + NEW_NODE_TOGGLE_STATE] = newNodeState;

                if (this.getToggleType() == 'server') {
                    var form = this.__treeRootElt.closest('form');
                    richfaces.submitForm(form, clientParams);
                } else {
                    toggleSource.__makeLoading();
                    clientParams[toggleSourceId + TRIGGER_NODE_AJAX_UPDATE] = newNodeState;
                    this.__ajaxSubmitFunction(event, toggleSourceId, clientParams, function() {
                        var treeNode = richfaces.$(toggleSourceId);
                        if (treeNode) {
                            treeNode.__resetLoading();
                        }
                    });
                }
            },

            getToggleType: function() {
                return this.__toggleType;
            },

            getSelectionType: function() {
                return this.__selectionType;
            },

            getTree: function() {
                return this;
            },

            __handleSelectionChange: function(newSelection) {
                var eventData = {
                    oldSelection: this.getSelection().getNodes(),
                    newSelection: newSelection.getNodes()
                };

                if (richfaces.Event.callHandler(this.__treeRootElt, "beforeselectionchange", eventData) === false) {
                    return;
                }

                this.__selectionInput.val(newSelection.getNodeString());

                if (this.getSelectionType() == 'client') {
                    this.__updateSelection(newSelection);
                } else {
                    this.__ajaxSubmitFunction(null, this.getId());
                }
            },

            __toggleSelection: function(node) {
                var newSelection = this.getSelection().cloneAndToggle(node);
                this.__handleSelectionChange(newSelection);
            },

            __addToSelection: function(node) {
                var newSelection = this.getSelection().cloneAndAdd(node);
                this.__handleSelectionChange(newSelection);
            },

            __updateSelectionFromInput: function() {
                this.__findSelectionInput();
                this.__updateSelection(new richfaces.ui.TreeNodeSet(this.__selectionInput.val()));
            },

            __updateSelection: function(newSelection) {

                var oldSelection = this.getSelection();

                oldSelection.each(function() {
                    this.__setSelected(false)
                });
                newSelection.each(function() {
                    this.__setSelected(true)
                });

                if (oldSelection.getNodeString() != newSelection.getNodeString()) {
                    richfaces.Event.callHandler(this.__treeRootElt, "selectionchange", {
                            oldSelection: oldSelection.getNodes(),
                            newSelection: newSelection.getNodes()
                        });
                }

                this.__selection = newSelection;
            },

            getSelection: function() {
                return this.__selection;
            },

            contextMenuAttach: function (menu) {
                var selector = "[id='" + this.id[0].id + "'] ";
                selector += (typeof menu.options.targetSelector === 'undefined')
                    ?  ".rf-trn-cnt" : menu.options.targetSelector;
                selector = jQuery.trim(selector);
                richfaces.Event.bind(selector, menu.options.showEvent, $.proxy(menu.__showHandler, menu), menu);
            }

        });

    // define super class link for Tree
    var $superTree = richfaces.ui.Tree.$super;

    richfaces.ui.TreeNodeSet = function() {
        this.init.apply(this, arguments);
    };

    //TODO - that's a single-node set, implement multi-node support!
    $.extend(richfaces.ui.TreeNodeSet.prototype, {

            init: function(nodeId) {
                this.__nodeId = nodeId;
            },

            contains: function(node) {
                if (node.getId) {
                    return this.__nodeId == node.getId();
                } else {
                    return this.__nodeId == node;
                }
            },

            getNodeString: function() {
                return this.__nodeId;
            },

            toString: function() {
                return this.getNodeString();
            },

            getNodes: function() {
                if (this.__nodeId) {
                    var node = richfaces.$(this.__nodeId);
                    if (node) {
                        return [node];
                    } else {
                        return null;
                    }
                }

                return [];
            },

            cloneAndAdd: function(node) {
                return new richfaces.ui.TreeNodeSet(node.getId());
            },

            cloneAndToggle: function(node) {
                var nodeId;
                if (this.contains(node)) {
                    nodeId = "";
                } else {
                    nodeId = node.getId();
                }

                return new richfaces.ui.TreeNodeSet(nodeId);
            },

            each: function(callback) {
                $.each(this.getNodes() || [], callback);
            }
        });

}(jQuery, RichFaces));