/**
 * Copyright (C) 2009 eXo Platform SAS.
 *
 * 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.
 */

package org.exoplatform.navigation.webui.component;

import java.util.Collection;
import java.util.HashMap;
import java.util.Locale;
import java.util.Map;

import org.exoplatform.navigation.webui.TreeNode;
import org.exoplatform.portal.application.PortalRequestContext;
import org.exoplatform.portal.config.UserACL;
import org.exoplatform.portal.config.UserPortalConfigService;
import org.exoplatform.portal.config.model.Page;
import org.exoplatform.portal.mop.Described.State;
import org.exoplatform.portal.mop.SiteKey;
import org.exoplatform.portal.mop.Visibility;
import org.exoplatform.portal.mop.description.DescriptionService;
import org.exoplatform.portal.mop.navigation.NavigationError;
import org.exoplatform.portal.mop.navigation.NavigationServiceException;
import org.exoplatform.portal.mop.navigation.Scope;
import org.exoplatform.portal.mop.page.PageContext;
import org.exoplatform.portal.mop.page.PageKey;
import org.exoplatform.portal.mop.page.PageService;
import org.exoplatform.portal.mop.user.UserNavigation;
import org.exoplatform.portal.mop.user.UserNode;
import org.exoplatform.portal.mop.user.UserNodeFilterConfig;
import org.exoplatform.portal.mop.user.UserPortal;
import org.exoplatform.portal.webui.page.UIPage;
import org.exoplatform.portal.webui.portal.UIPortalComposer;
import org.exoplatform.portal.webui.util.PortalDataMapper;
import org.exoplatform.portal.webui.util.Util;
import org.exoplatform.portal.webui.workspace.UIEditInlineWorkspace;
import org.exoplatform.portal.webui.workspace.UIPortalApplication;
import org.exoplatform.portal.webui.workspace.UIPortalToolPanel;
import org.exoplatform.portal.webui.workspace.UIWorkingWorkspace;
import org.exoplatform.portal.webui.workspace.UIPortalApplication.ComponentTab;
import org.exoplatform.portal.webui.workspace.UIPortalApplication.EditLevel;
import org.exoplatform.web.application.ApplicationMessage;
import org.exoplatform.webui.application.WebuiRequestContext;
import org.exoplatform.webui.config.annotation.ComponentConfig;
import org.exoplatform.webui.config.annotation.ComponentConfigs;
import org.exoplatform.webui.config.annotation.EventConfig;
import org.exoplatform.webui.core.UIApplication;
import org.exoplatform.webui.core.UIComponent;
import org.exoplatform.webui.core.UIContainer;
import org.exoplatform.webui.core.UIPopupWindow;
import org.exoplatform.webui.core.UIRightClickPopupMenu;
import org.exoplatform.webui.core.UITree;
import org.exoplatform.webui.event.Event;
import org.exoplatform.webui.event.Event.Phase;
import org.exoplatform.webui.event.EventListener;
import org.gatein.common.util.ParameterValidation;

/** Copied by The eXo Platform SARL Author May 28, 2009 3:07:15 PM */
@ComponentConfigs({
        @ComponentConfig(template = "system:/groovy/portal/webui/navigation/UINavigationNodeSelector.gtmpl", events = {
                @EventConfig(listeners = UINavigationNodeSelector.ChangeNodeActionListener.class),
                @EventConfig(listeners = UINavigationNodeSelector.NodeModifiedActionListener.class) }),
        @ComponentConfig(id = "NavigationNodePopupMenu", type = UIRightClickPopupMenu.class, template = "system:/groovy/webui/core/UIRightClickPopupMenu.gtmpl", events = {
                @EventConfig(listeners = UINavigationNodeSelector.AddNodeActionListener.class),
                @EventConfig(listeners = UINavigationNodeSelector.EditPageNodeActionListener.class),
                @EventConfig(listeners = UINavigationNodeSelector.EditSelectedNodeActionListener.class),
                @EventConfig(listeners = UINavigationNodeSelector.CopyNodeActionListener.class),
                @EventConfig(listeners = UINavigationNodeSelector.CutNodeActionListener.class),
                @EventConfig(listeners = UINavigationNodeSelector.CloneNodeActionListener.class),
                @EventConfig(listeners = UINavigationNodeSelector.PasteNodeActionListener.class),
                @EventConfig(listeners = UINavigationNodeSelector.MoveUpActionListener.class),
                @EventConfig(listeners = UINavigationNodeSelector.MoveDownActionListener.class),
                @EventConfig(listeners = UINavigationNodeSelector.DeleteNodeActionListener.class, confirm = "UIPageNodeSelector.deleteNavigation") }),
        @ComponentConfig(id = "UINavigationNodeSelectorPopupMenu", type = UIRightClickPopupMenu.class, template = "system:/groovy/webui/core/UIRightClickPopupMenu.gtmpl", events = {
                @EventConfig(listeners = UINavigationNodeSelector.AddNodeActionListener.class),
                @EventConfig(listeners = UINavigationNodeSelector.PasteNodeActionListener.class) }) })
public class UINavigationNodeSelector extends UIContainer {
    private UserNavigation edittedNavigation;

    /**
     * This field holds transient copy of edittedTreeNodeData, which is used when user pastes the content to a new tree node
     */
    private TreeNode copyOfTreeNodeData;

    private TreeNode rootNode;

    private UserPortal userPortal;

    private UserNodeFilterConfig filterConfig;

    private Map<String, Map<Locale, State>> userNodeLabels;

    private static final Scope DEFAULT_SCOPE = Scope.GRANDCHILDREN;
    private Scope navigationScope = DEFAULT_SCOPE;

    public UINavigationNodeSelector() throws Exception {
        UIRightClickPopupMenu rightClickPopup = addChild(UIRightClickPopupMenu.class, "UINavigationNodeSelectorPopupMenu", null)
                .setRendered(true);
        rightClickPopup.setActions(new String[] { "AddNode", "PasteNode" });

        UITree uiTree = addChild(UITree.class, null, "TreeNodeSelector");
        uiTree.setIcon("DefaultPageIcon");
        uiTree.setSelectedIcon("DefaultPageIcon");
        uiTree.setBeanIdField("Id");
        uiTree.setBeanChildCountField("childrenCount");
        uiTree.setBeanLabelField("encodedResolvedLabel");
        uiTree.setBeanIconField("icon");

        UIRightClickPopupMenu uiPopupMenu = createUIComponent(UIRightClickPopupMenu.class, "NavigationNodePopupMenu", null);
        uiPopupMenu.setActions(new String[] { "AddNode", "EditPageNode", "EditSelectedNode", "CopyNode", "CloneNode",
                "CutNode", "DeleteNode", "MoveUp", "MoveDown" });
        uiTree.setUIRightClickPopupMenu(uiPopupMenu);

        userNodeLabels = new HashMap<String, Map<Locale, State>>();
    }

    public void setUserNodeLabels(Map<String, Map<Locale, State>> labels) {
        this.userNodeLabels = labels;
    }

    public Map<String, Map<Locale, State>> getUserNodeLabels() {
        return this.userNodeLabels;
    }

    /**
     * Init the UITree wrapped in UINavigationNodeSelector
     *
     * @throws Exception
     */
    public void initTreeData() throws Exception {
        if (edittedNavigation == null || userPortal == null) {
            throw new IllegalStateException("edittedNavigation and userPortal must be initialized first");
        }

        try {
            this.rootNode = new TreeNode(edittedNavigation, userPortal.getNode(edittedNavigation, navigationScope,
                    filterConfig, null));

            TreeNode node = this.rootNode;
            if (this.rootNode.getChildren().size() > 0) {
                node = rebaseNode(this.rootNode.getChild(0), navigationScope);
                if (node == null) {
                    initTreeData();
                    return;
                }
            }
            selectNode(node);
        } catch (Exception ex) {
            // Navigation deleted --> close the editor
            this.rootNode = null;

            WebuiRequestContext context = WebuiRequestContext.getCurrentInstance();
            context.getUIApplication().addMessage(
                    new ApplicationMessage("UINavigationNodeSelector.msg." + NavigationError.NAVIGATION_NO_SITE.name(), null,
                            ApplicationMessage.ERROR));

            UIPopupWindow popup = getAncestorOfType(UIPopupWindow.class);
            popup.createEvent("ClosePopup", Phase.PROCESS, context).broadcast();

            PortalRequestContext prContext = Util.getPortalRequestContext();
            UIWorkingWorkspace uiWorkingWS = Util.getUIPortalApplication().getChild(UIWorkingWorkspace.class);
            prContext.addUIComponentToUpdateByAjax(uiWorkingWS);
            prContext.setFullRender(true);
        }
    }

    public TreeNode selectNode(TreeNode node) {
        if (node == null) {
            return null;
        }

        UITree tree = getChild(UITree.class);
        tree.setSelected(node);
        if (node.getId().equals(rootNode.getId())) {
            tree.setChildren(null);
            tree.setSibbling(node.getChildren());
            tree.setParentSelected(node);
        } else {
            TreeNode parentNode = node.getParent();
            tree.setChildren(node.getChildren());
            tree.setSibbling(parentNode.getChildren());
            tree.setParentSelected(parentNode);
        }
        return node;
    }

    public TreeNode rebaseNode(TreeNode treeNode, Scope scope) {
        if (treeNode == null || treeNode.getNode() == null) {
            return null;
        }

        UserNode userNode = treeNode.getNode();
        if (userNode.getId() == null) {
            // Transient node
            return treeNode;
        }

        userPortal.rebaseNode(userNode, scope, getRootNode());
        // this line return null if node has been deleted
        return findNode(treeNode.getId());
    }

    public void save() {
        WebuiRequestContext context = WebuiRequestContext.getCurrentInstance();
        try {
            userPortal.saveNode(getRootNode().getNode(), getRootNode());
            DescriptionService descriptionService = getApplicationComponent(DescriptionService.class);
            Map<String, Map<Locale, State>> i18nizedLabels = this.userNodeLabels;

            for (String treeNodeId : i18nizedLabels.keySet()) {
                TreeNode node = findNode(treeNodeId);
                if (node != null) {
                    Map<Locale, State> labels = i18nizedLabels.get(treeNodeId);
                    if (labels != null && labels.size() > 0) {
                        descriptionService.setDescriptions(node.getNode().getId(), labels);
                    }
                }

            }
        } catch (NavigationServiceException ex) {
            context.getUIApplication().addMessage(
                    new ApplicationMessage("UINavigationNodeSelector.msg." + ex.getError().name(), null,
                            ApplicationMessage.ERROR));
        }
    }

    public TreeNode getCopyNode() {
        return copyOfTreeNodeData;
    }

    public void setCopyNode(TreeNode copyNode) {
        this.copyOfTreeNodeData = copyNode;
    }

    public TreeNode getRootNode() {
        return rootNode;
    }

    public void setUserPortal(UserPortal userPortal) {
        this.userPortal = userPortal;
        setFilterConfig(UserNodeFilterConfig.builder().withReadWriteCheck().build());
    }

    private void setFilterConfig(UserNodeFilterConfig config) {
        this.filterConfig = config;
    }

    public void setEdittedNavigation(UserNavigation nav) {
        this.edittedNavigation = nav;
    }

    public UserNavigation getEdittedNavigation() {
        return this.edittedNavigation;
    }

    public TreeNode findNode(String nodeID) {
        if (getRootNode() == null) {
            return null;
        }
        return getRootNode().findNode(nodeID);
    }

    public void setScope(Scope scope) {
        this.navigationScope = scope;
    }

    public Scope getScope() {
        return this.navigationScope;
    }

    private void invokeI18NizedLabels(TreeNode node) {
        DescriptionService descriptionService = this.getApplicationComponent(DescriptionService.class);
        try {
            Map<Locale, State> labels = descriptionService.getDescriptions(node.getId());
            node.setI18nizedLabels(labels);
        } catch (NullPointerException npe) {
            // set label list is null if Described mixin has been removed or not exists.
            node.setI18nizedLabels(null);
        }
    }

    public abstract static class BaseActionListener<T> extends EventListener<T> {
        protected TreeNode rebaseNode(TreeNode node, UINavigationNodeSelector selector) throws Exception {
            return rebaseNode(node, selector.getScope(), selector);
        }

        protected TreeNode rebaseNode(TreeNode node, Scope scope, UINavigationNodeSelector selector) throws Exception {
            WebuiRequestContext context = WebuiRequestContext.getCurrentInstance();
            TreeNode rebased = selector.rebaseNode(node, scope);
            if (rebased == null) {
                selector.getUserNodeLabels().remove(node.getId());

                context.getUIApplication().addMessage(
                        new ApplicationMessage("UINavigationNodeSelector.msg.staleData", null, ApplicationMessage.WARNING));
                selector.selectNode(selector.getRootNode());
                context.addUIComponentToUpdateByAjax(selector);
            }
            return rebased;
        }

        protected void handleError(NavigationError error, UINavigationNodeSelector selector) throws Exception {
            selector.initTreeData();
            selector.getUserNodeLabels().clear();
            if (selector.getRootNode() != null) {
                WebuiRequestContext context = WebuiRequestContext.getCurrentInstance();
                UIApplication uiApp = context.getUIApplication();
                uiApp.addMessage(new ApplicationMessage("UINavigationNodeSelector.msg." + error.name(), null,
                        ApplicationMessage.ERROR));
            }
        }
    }

    public static class ChangeNodeActionListener extends BaseActionListener<UITree> {
        public void execute(Event<UITree> event) throws Exception {
            WebuiRequestContext context = event.getRequestContext();
            UINavigationNodeSelector uiNodeSelector = event.getSource().getParent();

            String nodeID = context.getRequestParameter(OBJECTID);
            TreeNode node = uiNodeSelector.findNode(nodeID);

            try {
                node = rebaseNode(node, uiNodeSelector);
            } catch (NavigationServiceException ex) {
                handleError(ex.getError(), uiNodeSelector);
                return;
            }

            uiNodeSelector.selectNode(node);
            context.addUIComponentToUpdateByAjax(uiNodeSelector);
        }
    }

    public static class AddNodeActionListener extends BaseActionListener<UIRightClickPopupMenu> {
        public void execute(Event<UIRightClickPopupMenu> event) throws Exception {
            WebuiRequestContext context = event.getRequestContext();
            UIRightClickPopupMenu uiPopupMenu = event.getSource();
            UINavigationNodeSelector uiNodeSelector = uiPopupMenu.getAncestorOfType(UINavigationNodeSelector.class);

            String nodeID = context.getRequestParameter(UIComponent.OBJECTID);
            TreeNode node;
            if (ParameterValidation.isNullOrEmpty(nodeID)) {
                node = uiNodeSelector.getSelectedNode();
                if (node == null) {
                    node = uiNodeSelector.getRootNode();
                }
            } else {
                node = uiNodeSelector.findNode(nodeID);
            }

            try {
                node = rebaseNode(node, uiNodeSelector);
                if (node == null)
                    return;
            } catch (NavigationServiceException ex) {
                handleError(ex.getError(), uiNodeSelector);
                return;
            }

            UIPopupWindow uiManagementPopup = uiNodeSelector.getAncestorOfType(UIPopupWindow.class);
            UIPageNodeForm uiNodeForm = uiManagementPopup.createUIComponent(UIPageNodeForm.class, null, null);
            uiNodeForm.setValues(null);
            uiManagementPopup.setUIComponent(uiNodeForm);

            uiNodeForm.setSelectedParent(node);
            UserNavigation edittedNavigation = uiNodeSelector.getEdittedNavigation();
            uiNodeForm.setContextPageNavigation(edittedNavigation);
            uiManagementPopup.setWindowSize(800, 500);
            event.getRequestContext().addUIComponentToUpdateByAjax(uiManagementPopup.getParent());
        }
    }

    public static class NodeModifiedActionListener extends BaseActionListener<UINavigationNodeSelector> {
        @Override
        public void execute(Event<UINavigationNodeSelector> event) throws Exception {
            UINavigationNodeSelector uiNodeSelector = event.getSource();

            try {
                rebaseNode(uiNodeSelector.getRootNode(), uiNodeSelector);
            } catch (NavigationServiceException ex) {
                handleError(ex.getError(), uiNodeSelector);
            }
        }
    }

    public static class EditPageNodeActionListener extends BaseActionListener<UIRightClickPopupMenu> {
        public void execute(Event<UIRightClickPopupMenu> event) throws Exception {
            UIPortalApplication uiApp = Util.getUIPortalApplication();
            UIRightClickPopupMenu popupMenu = event.getSource();
            UINavigationNodeSelector uiNodeSelector = popupMenu.getAncestorOfType(UINavigationNodeSelector.class);

            String nodeID = event.getRequestContext().getRequestParameter(UIComponent.OBJECTID);
            TreeNode node = uiNodeSelector.findNode(nodeID);
            try {
                node = rebaseNode(node, uiNodeSelector);
                if (node == null)
                    return;
            } catch (NavigationServiceException ex) {
                handleError(ex.getError(), uiNodeSelector);
                return;
            }

            UserPortalConfigService userService = uiNodeSelector.getApplicationComponent(UserPortalConfigService.class);

            // get selected page
            String pageId = node.getPageRef();
            PageContext pageContext = (pageId != null) ? userService.getPageService().loadPage(PageKey.parse(pageId)) : null;
            if (pageContext != null) {
                UserACL userACL = uiApp.getApplicationComponent(UserACL.class);
                if (!userACL.hasEditPermission(pageContext)) {
                    uiApp.addMessage(new ApplicationMessage("UIPageBrowser.msg.UserNotPermission", new String[] { pageId }, 1));
                    return;
                }

                uiApp.setDefaultEditMode(ComponentTab.APPLICATIONS, EditLevel.EDIT_PAGE);

                UIWorkingWorkspace uiWorkingWS = uiApp.getChildById(UIPortalApplication.UI_WORKING_WS_ID);
                UIPortalToolPanel uiToolPanel = uiWorkingWS.findFirstComponentOfType(UIPortalToolPanel.class).setRendered(true);
                uiWorkingWS.setRenderedChild(UIEditInlineWorkspace.class);

                UIPortalComposer portalComposer = uiWorkingWS.findFirstComponentOfType(UIPortalComposer.class)
                        .setRendered(true);
                portalComposer.setShowControl(true);
                portalComposer.setEditted(false);
                portalComposer.setCollapse(false);
                portalComposer.setId(UIPortalComposer.UIPAGE_EDITOR);
                portalComposer.setComponentConfig(UIPortalComposer.class, UIPortalComposer.UIPAGE_EDITOR);

                uiToolPanel.setShowMaskLayer(false);
                uiToolPanel.setWorkingComponent(UIPage.class, null);
                UIPage uiPage = (UIPage) uiToolPanel.getUIComponent();

                if (pageContext.getState().getDisplayName() == null)
                    pageContext.getState().builder().displayName(node.getLabel());

                Page page = userService.getDataStorage().getPage(pageId);
                pageContext.update(page);

                // convert Page to UIPage
                PortalDataMapper.toUIPage(uiPage, page);
                Util.getPortalRequestContext().addUIComponentToUpdateByAjax(uiWorkingWS);
                Util.getPortalRequestContext().ignoreAJAXUpdateOnPortlets(true);
            } else {
                uiApp.addMessage(new ApplicationMessage("UIPageNodeSelector.msg.notAvailable", null));
            }
        }
    }

    public static class EditSelectedNodeActionListener extends BaseActionListener<UIRightClickPopupMenu> {
        public void execute(Event<UIRightClickPopupMenu> event) throws Exception {
            WebuiRequestContext context = event.getRequestContext();
            UIRightClickPopupMenu popupMenu = event.getSource();
            UINavigationNodeSelector uiNodeSelector = popupMenu.getAncestorOfType(UINavigationNodeSelector.class);

            String nodeID = event.getRequestContext().getRequestParameter(UIComponent.OBJECTID);
            TreeNode node = uiNodeSelector.findNode(nodeID);
            try {
                node = rebaseNode(node, uiNodeSelector);
                if (node == null)
                    return;
            } catch (NavigationServiceException ex) {
                handleError(ex.getError(), uiNodeSelector);
                return;
            }

            UIApplication uiApp = context.getUIApplication();
            UserPortalConfigService service = uiApp.getApplicationComponent(UserPortalConfigService.class);
            String pageId = node.getPageRef();
            // Page page = (pageId != null) ? service.getPage(pageId) : null;
            PageContext page = (pageId != null) ? service.getPageService().loadPage(PageKey.parse(pageId)) : null;
            if (page != null) {
                UserACL userACL = uiApp.getApplicationComponent(UserACL.class);
                if (!userACL.hasPermission(page)) {
                    uiApp.addMessage(new ApplicationMessage("UIPageBrowser.msg.UserNotPermission", new String[] { pageId }, 1));
                    return;
                }
            }

            if (node.getI18nizedLabels() == null) {
                uiNodeSelector.invokeI18NizedLabels(node);
            }

            UIPopupWindow uiManagementPopup = uiNodeSelector.getAncestorOfType(UIPopupWindow.class);
            UIPageNodeForm uiNodeForm = uiApp.createUIComponent(UIPageNodeForm.class, null, null);
            uiManagementPopup.setUIComponent(uiNodeForm);

            UserNavigation edittedNav = uiNodeSelector.getEdittedNavigation();
            uiNodeForm.setContextPageNavigation(edittedNav);
            uiNodeForm.setValues(node);
            uiNodeForm.setSelectedParent(node.getParent());
            uiManagementPopup.setWindowSize(800, 500);
            event.getRequestContext().addUIComponentToUpdateByAjax(uiManagementPopup.getParent());
        }
    }

    public static class CopyNodeActionListener extends BaseActionListener<UIRightClickPopupMenu> {
        public void execute(Event<UIRightClickPopupMenu> event) throws Exception {
            WebuiRequestContext context = event.getRequestContext();
            UINavigationNodeSelector uiNodeSelector = event.getSource().getAncestorOfType(UINavigationNodeSelector.class);
            event.getRequestContext().addUIComponentToUpdateByAjax(uiNodeSelector);

            String nodeID = context.getRequestParameter(UIComponent.OBJECTID);
            TreeNode node = uiNodeSelector.findNode(nodeID);
            if (Visibility.SYSTEM.equals(node.getVisibility())) {
                UIApplication uiApp = context.getUIApplication();
                uiApp.addMessage(new ApplicationMessage("UINavigationNodeSelector.msg.systemnode-copyclone", null));
                return;
            }
            try {
                node = rebaseNode(node, Scope.ALL, uiNodeSelector);
                if (node == null)
                    return;
            } catch (NavigationServiceException ex) {
                handleError(ex.getError(), uiNodeSelector);
                return;
            }

            node.setDeleteNode(false);
            if (node.getI18nizedLabels() == null) {
                uiNodeSelector.invokeI18NizedLabels(node);
            }
            uiNodeSelector.setCopyNode(node);
            event.getSource().setActions(
                    new String[] { "AddNode", "EditPageNode", "EditSelectedNode", "CopyNode", "CloneNode", "CutNode",
                            "PasteNode", "DeleteNode", "MoveUp", "MoveDown" });
        }
    }

    public static class CutNodeActionListener extends BaseActionListener<UIRightClickPopupMenu> {
        public void execute(Event<UIRightClickPopupMenu> event) throws Exception {
            WebuiRequestContext context = event.getRequestContext();
            UINavigationNodeSelector uiNodeSelector = event.getSource().getAncestorOfType(UINavigationNodeSelector.class);
            context.addUIComponentToUpdateByAjax(uiNodeSelector);

            String nodeID = context.getRequestParameter(UIComponent.OBJECTID);
            TreeNode node = uiNodeSelector.findNode(nodeID);
            try {
                node = rebaseNode(node, Scope.SINGLE, uiNodeSelector);
                if (node == null)
                    return;
            } catch (NavigationServiceException ex) {
                handleError(ex.getError(), uiNodeSelector);
                return;
            }

            if (node != null && Visibility.SYSTEM.equals(node.getVisibility())) {
                context.getUIApplication().addMessage(
                        new ApplicationMessage("UINavigationNodeSelector.msg.systemnode-move", null));
                return;
            }

            node.setDeleteNode(true);
            uiNodeSelector.setCopyNode(node);
            event.getSource().setActions(
                    new String[] { "AddNode", "EditPageNode", "EditSelectedNode", "CopyNode", "CloneNode", "CutNode",
                            "PasteNode", "DeleteNode", "MoveUp", "MoveDown" });
        }
    }

    public static class CloneNodeActionListener extends CopyNodeActionListener {
        public void execute(Event<UIRightClickPopupMenu> event) throws Exception {
            super.execute(event);
            UINavigationNodeSelector uiNodeSelector = event.getSource().getAncestorOfType(UINavigationNodeSelector.class);
            TreeNode currNode = uiNodeSelector.getCopyNode();
            String nodeID = event.getRequestContext().getRequestParameter(UIComponent.OBJECTID);
            if (currNode == null)
                return;
            else if (currNode.getId().equals(nodeID))
                currNode.setCloneNode(true);

            if (currNode.getI18nizedLabels() == null) {
                uiNodeSelector.invokeI18NizedLabels(currNode);
            }
        }
    }

    public static class PasteNodeActionListener extends BaseActionListener<UIRightClickPopupMenu> {
        private UINavigationNodeSelector uiNodeSelector;

        private PageService pageService;

        public void execute(Event<UIRightClickPopupMenu> event) throws Exception {
            WebuiRequestContext context = event.getRequestContext();
            UIRightClickPopupMenu uiPopupMenu = event.getSource();
            uiNodeSelector = uiPopupMenu.getAncestorOfType(UINavigationNodeSelector.class);
            context.addUIComponentToUpdateByAjax(uiNodeSelector);

            String nodeID = context.getRequestParameter(UIComponent.OBJECTID);
            TreeNode targetNode = uiNodeSelector.findNode(nodeID);
            TreeNode sourceNode = uiNodeSelector.getCopyNode();
            if (sourceNode == null)
                return;

            try {
                targetNode = rebaseNode(targetNode, uiNodeSelector);
                if (targetNode == null)
                    return;
            } catch (NavigationServiceException ex) {
                handleError(ex.getError(), uiNodeSelector);
                return;
            }

            if (sourceNode.getId().equals(targetNode.getId())) {
                context.getUIApplication().addMessage(
                        new ApplicationMessage("UIPageNodeSelector.msg.paste.sameSrcAndDes", null));
                return;
            }

            if (isExistChild(targetNode, sourceNode)) {
                context.getUIApplication().addMessage(new ApplicationMessage("UIPageNodeSelector.msg.paste.sameName", null));
                return;
            }

            if(isExistsInTree(sourceNode, targetNode)) {
                context.getUIApplication().addMessage(new ApplicationMessage("UIPageNodeSelector.msg.paste.wrongLocation", null, ApplicationMessage.WARNING));
                return;
            }

            UITree uitree = uiNodeSelector.getChild(UITree.class);
            UIRightClickPopupMenu popup = uitree.getUIRightClickPopupMenu();
            popup.setActions(new String[] { "AddNode", "EditPageNode", "EditSelectedNode", "CopyNode", "CutNode", "CloneNode",
                    "DeleteNode", "MoveUp", "MoveDown" });
            uiNodeSelector.setCopyNode(null);

            if (uiNodeSelector.findNode(sourceNode.getId()) == null) {
                context.getUIApplication().addMessage(
                        new ApplicationMessage("UINavigationNodeSelector.msg.copiedNode.deleted", null,
                                ApplicationMessage.WARNING));
                uiNodeSelector.selectNode(uiNodeSelector.getRootNode());
                return;
            }

            if (sourceNode.isDeleteNode()) {
                targetNode.addChild(sourceNode);
                uiNodeSelector.selectNode(targetNode);
                return;
            }

            pageService = uiNodeSelector.getApplicationComponent(PageService.class);
            pasteNode(sourceNode, targetNode, sourceNode.isCloneNode());
            uiNodeSelector.selectNode(targetNode);
        }

        private TreeNode pasteNode(TreeNode sourceNode, TreeNode parent, boolean isClone) throws Exception {
            TreeNode node = parent.addChild(sourceNode.getName());
            node.setLabel(sourceNode.getLabel());
            node.setVisibility(sourceNode.getVisibility());
            node.setIcon(sourceNode.getIcon());
            node.setStartPublicationTime(sourceNode.getStartPublicationTime());
            node.setEndPublicationTime(sourceNode.getEndPublicationTime());

            if (isClone) {
                String pageName = "page" + node.hashCode();
                node.setPageRef(clonePageFromNode(sourceNode, pageName, sourceNode.getPageNavigation().getKey()));
            } else {
                node.setPageRef(sourceNode.getPageRef());
            }

            for (TreeNode child : sourceNode.getChildren()) {
                pasteNode(child, node, isClone);
            }

            node.setI18nizedLabels(sourceNode.getI18nizedLabels());
            uiNodeSelector.getUserNodeLabels().put(node.getId(), node.getI18nizedLabels());
            return node;
        }

        private String clonePageFromNode(TreeNode node, String pageName, SiteKey siteKey) {
            String pageId = node.getPageRef();
            if (pageId != null) {
                PageKey sourceKey = PageKey.parse(pageId);
                PageContext page = pageService.loadPage(sourceKey);
                if (page != null) {
                    page = pageService.clone(sourceKey, siteKey.page(pageName));
                    return page.getKey().format();
                }
            }
            return null;
        }

        private boolean isExistChild(TreeNode parent, TreeNode child) {
            return parent != null && parent.getChild(child.getName()) != null;
        }

        private boolean isExistsInTree(TreeNode parent, TreeNode child) {
            if(parent == null || child == null) {
                return false;
            }
            TreeNode p = child.getParent();
            while (p != null) {
                if(parent.getId().equals(p.getId())) {
                    return true;
                }
                p = p.getParent();
            }
            return false;
        }
    }

    public static class MoveUpActionListener extends BaseActionListener<UIRightClickPopupMenu> {
        public void execute(Event<UIRightClickPopupMenu> event) throws Exception {
            moveNode(event, -1);
        }

        protected void moveNode(Event<UIRightClickPopupMenu> event, int i) throws Exception {
            WebuiRequestContext context = event.getRequestContext();
            UINavigationNodeSelector uiNodeSelector = event.getSource().getAncestorOfType(UINavigationNodeSelector.class);
            context.addUIComponentToUpdateByAjax(uiNodeSelector.getParent());

            String nodeID = context.getRequestParameter(UIComponent.OBJECTID);
            TreeNode targetNode = uiNodeSelector.findNode(nodeID);
            // This happen when browser's not sync with server
            if (targetNode == null)
                return;

            TreeNode parentNode = targetNode.getParent();
            try {
                parentNode = rebaseNode(parentNode, uiNodeSelector);
                if (parentNode == null)
                    return;
                // After update the parentNode, maybe targetNode has been deleted or moved
                TreeNode temp = parentNode.getChild(targetNode.getName());
                if (temp == null || !temp.getId().equals(targetNode.getId())) {
                    context.getUIApplication().addMessage(
                            new ApplicationMessage("UINavigationNodeSelector.msg.staleData", null, ApplicationMessage.WARNING));
                    uiNodeSelector.selectNode(uiNodeSelector.getRootNode());
                    context.addUIComponentToUpdateByAjax(uiNodeSelector);
                    return;
                }
            } catch (NavigationServiceException ex) {
                handleError(ex.getError(), uiNodeSelector);
                return;
            }

            Collection<TreeNode> children = parentNode.getChildren();

            int k;
            for (k = 0; k < children.size(); k++) {
                if (parentNode.getChild(k).getId().equals(targetNode.getId())) {
                    break;
                }
            }

            if (k == 0 && i == -1) {
                return;
            }
            if (k == children.size() - 1 && i == 2) {
                return;
            }

            parentNode.addChild(k + i, targetNode);

            // These lines help to refresh the tree
            TreeNode selectedNode = uiNodeSelector.getSelectedNode();
            uiNodeSelector.selectNode(parentNode);
            uiNodeSelector.selectNode(selectedNode);
        }
    }

    public static class MoveDownActionListener extends UINavigationNodeSelector.MoveUpActionListener {
        public void execute(Event<UIRightClickPopupMenu> event) throws Exception {
            super.moveNode(event, 2);
        }
    }

    public static class DeleteNodeActionListener extends EventListener<UIRightClickPopupMenu> {
        public void execute(Event<UIRightClickPopupMenu> event) throws Exception {
            WebuiRequestContext pcontext = event.getRequestContext();
            UINavigationNodeSelector uiNodeSelector = event.getSource().getAncestorOfType(UINavigationNodeSelector.class);
            pcontext.addUIComponentToUpdateByAjax(uiNodeSelector);

            String nodeID = pcontext.getRequestParameter(UIComponent.OBJECTID);
            TreeNode childNode = uiNodeSelector.findNode(nodeID);
            if (childNode == null) {
                return;
            }
            TreeNode parentNode = childNode.getParent();

            if (Visibility.SYSTEM.equals(childNode.getVisibility())) {
                UIApplication uiApp = pcontext.getUIApplication();
                uiApp.addMessage(new ApplicationMessage("UINavigationNodeSelector.msg.systemnode-delete", null));
                return;
            }
            uiNodeSelector.getUserNodeLabels().remove(childNode.getId());
            parentNode.removeChild(childNode);
            uiNodeSelector.selectNode(parentNode);
        }
    }

    public TreeNode getSelectedNode() {
        return getChild(UITree.class).getSelected();
    }
}
