/*
 * RHQ Management Platform
 * Copyright (C) 2005-2008 Red Hat, Inc.
 * All rights reserved.
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation version 2 of the License.
 *
 * This program 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 General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
 */
package org.rhq.enterprise.gui.navigation.group;

import java.io.IOException;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;

import javax.faces.application.FacesMessage;
import javax.faces.context.FacesContext;
import javax.servlet.http.HttpServletResponse;

import org.richfaces.component.UITree;
import org.richfaces.component.html.HtmlTree;
import org.richfaces.component.state.TreeState;
import org.richfaces.component.state.TreeStateAdvisor;
import org.richfaces.model.TreeRowKey;

import org.rhq.core.domain.auth.Subject;
import org.rhq.core.domain.resource.flyweight.AutoGroupCompositeFlyweight;
import org.rhq.core.domain.resource.group.composite.AutoGroupComposite;
import org.rhq.core.domain.resource.group.ResourceGroup;
import org.rhq.core.gui.util.FacesContextUtility;
import org.rhq.enterprise.gui.navigation.resource.ResourceTreeNode;
import org.rhq.enterprise.gui.util.EnterpriseFacesContextUtility;
import org.rhq.enterprise.server.resource.cluster.ClusterKey;
import org.rhq.enterprise.server.resource.cluster.ClusterManagerLocal;
import org.rhq.enterprise.server.resource.group.ResourceGroupManagerLocal;
import org.rhq.enterprise.server.util.LookupUtil;

/**
 * @author Greg Hinkle
 */
public class ResourceGroupTreeStateAdvisor implements TreeStateAdvisor {

    private static class CurrentSelection {
        public ResourceGroup resourceGroup;
        public ClusterKey clusterKey;
    }
    
    private CurrentSelection currentSelection;

    private Set<ResourceGroupTreeNode> openNodes = new HashSet<ResourceGroupTreeNode>();
    
    private CurrentSelection getCurrentSelection() {
        int selectedGroupId = getSelectedGroupId();
        if (currentSelection == null || currentSelection.resourceGroup.getId() != selectedGroupId) {
            ResourceGroupManagerLocal groupManager = LookupUtil.getResourceGroupManager();
            currentSelection = new CurrentSelection();
            currentSelection.resourceGroup = groupManager.getResourceGroupById(EnterpriseFacesContextUtility.getSubject(),
                selectedGroupId, null);
            if (!currentSelection.resourceGroup.isVisible()) {
                currentSelection.clusterKey = ClusterKey.valueOf(currentSelection.resourceGroup.getClusterKey());
            }
        }
        
        return currentSelection;
    }

    private static int getSelectedGroupId() {
        String groupId = FacesContextUtility.getOptionalRequestParameter("groupId");
        return Integer.parseInt(groupId);
    }

    public void changeExpandListener(org.richfaces.event.NodeExpandedEvent e) {
        HtmlTree tree = (HtmlTree) e.getComponent();
        TreeState state = (TreeState) tree.getComponentState();

        //check if we're collapsing a parent of currently selected node.
        //if we do, change the focus to the parent
        if (state.getSelectedNode() != null) {
            boolean closingParent = false;

            TreeRowKey<?> key = (TreeRowKey<?>) tree.getRowKey();
            ResourceGroupTreeNode node = (ResourceGroupTreeNode) tree.getRowData(key);
            ResourceGroupTreeNode selectedNode = (ResourceGroupTreeNode) tree.getRowData(state.getSelectedNode());

            ResourceGroupTreeNode traverseCheckNode = selectedNode.getParent();
            while (traverseCheckNode != null) {
                if (node.equals(traverseCheckNode)) {
                    closingParent = true;
                    break;
                }
                traverseCheckNode = traverseCheckNode.getParent();
            }

            if (closingParent) {
                if (redirectTo(node)) {
                    state.setSelected(key);
                    openNodes.remove(node);
                    
                    //this is nasty hack. We need some kind of flag that would persist only for the remainder
                    //of this request to advertise that no more open/closed states should be made in this request. 
                    //The tree is request scoped, so setting this flag will not persist to the next request. 
                    //In the case of the tree, setting this to true in this listener has no side-effects.
                    tree.setBypassUpdates(true);
                } else if (!redirectTo(selectedNode)) {
                    FacesContext.getCurrentInstance().addMessage("leftNavTreeForm:leftNavTree", 
                        new FacesMessage(FacesMessage.SEVERITY_WARN, "Failed to re-expand node that shouldn't be collapsed.", null));                    
                }
            } else {
                if (openNodes.contains(node)) {
                    openNodes.remove(node);
                } else {
                    openNodes.add(node);
                }
            }
        }
    }

    public Boolean adviseNodeOpened(UITree tree) {
        TreeRowKey<?> key = (TreeRowKey<?>) tree.getRowKey();

        if (key != null) {
            ResourceGroupTreeNode node = (ResourceGroupTreeNode) tree.getRowData(key);
            if (node.getParent() == null) {
                return true;
            }

            CurrentSelection currentSelection = getCurrentSelection();
            
            //only update the state of open nodes in the preopen check
            //if we're not finishing the request in which a parent
            //of currently selected node was requested to close.
            //If we did update the open node states in the "remainder"
            //of such request the redirect that results from it would
            //get wrong information and the parent wouldn't appear closed
            //(because it'd had been re-opened in the below preopen call).
            //@see changeExpandListener for more nasty details.
            boolean setOpenStates = !tree.isBypassUpdates();
            
            if (preopen(node, currentSelection, setOpenStates)) {
                return true;
            }

            return openNodes.contains(node);
        }
        return null;
    }

    private boolean preopen(ResourceGroupTreeNode resourceTreeNode, CurrentSelection currentSelection, boolean setOpenState) {
        ResourceGroup currentGroup = currentSelection.resourceGroup;
        ClusterKey selectedClusterKey = currentSelection.clusterKey;
        
        boolean ret = false;
        for (ResourceGroupTreeNode child : resourceTreeNode.getChildren()) {
            if (child.getData() instanceof ClusterKey) {
                if (((ClusterKey) child.getData()).equals(selectedClusterKey)) {
                    ret = true;
                    break;
                }
            } else if (child.getData() instanceof ResourceGroup) {
                if (currentGroup.getId() == ((ResourceGroup) child.getData()).getId()) {
                    ret = true;
                    break;
                }
            }
            
            if (preopen(child, currentSelection, setOpenState)) {
                ret = true;
                break;
            }
        }

        if (setOpenState && ret) {
            openNodes.add(resourceTreeNode);
        }
        
        return ret;
    }

    public void nodeSelectListener(org.richfaces.event.NodeSelectedEvent e) {
        HtmlTree tree = (HtmlTree) e.getComponent();
        TreeState state = (TreeState) ((HtmlTree) tree).getComponentState();

        TreeRowKey<?> key = (TreeRowKey<?>) tree.getRowKey();
        ResourceGroupTreeNode node = (ResourceGroupTreeNode) tree.getRowData(key);

        if (node != null && !redirectTo(node)) {
            state.setSelected(e.getOldSelection());
        }
    }

    public Boolean adviseNodeSelected(UITree tree) {

        CurrentSelection currentSelection = getCurrentSelection();
        ResourceGroupTreeNode node = (ResourceGroupTreeNode) tree.getRowData(tree.getRowKey());

        if (node.getData() instanceof ResourceGroup) {
            return (currentSelection.resourceGroup.getId() == ((ResourceGroup) node.getData()).getId());
        } else if (node.getData() instanceof ClusterKey) {
            ClusterKey key = (ClusterKey) node.getData();

            if (currentSelection.clusterKey != null && currentSelection.clusterKey.equals(key)) {
                return true;
            }
        }

        return false;
    }

    /**
     * @return false if there was an error redirecting to the target location
     */
    private boolean redirectTo(ResourceGroupTreeNode node) {
        HttpServletResponse response = (HttpServletResponse) FacesContextUtility.getFacesContext().getExternalContext()
            .getResponse();

        Subject subject = EnterpriseFacesContextUtility.getSubject();

        String path = "";
        if (node.getData() instanceof ClusterKey) {

            ClusterManagerLocal clusterManager = LookupUtil.getClusterManager();
            ResourceGroup group = clusterManager.createAutoClusterBackingGroup(subject, (ClusterKey) node.getData(),
                true);

            path = "/rhq/group/inventory/view.xhtml";

            path += ("?groupId=" + group.getId() + "&parentGroupId=" + ((ClusterKey) node.getData())
                .getClusterGroupId());

        } else if (node.getData() instanceof AutoGroupCompositeFlyweight) {
            FacesContext.getCurrentInstance().addMessage("leftNavGroupTreeForm:leftNavGroupTree",
                new FacesMessage(FacesMessage.SEVERITY_WARN, "No cluster autogroup views available", null));
            return false;
        } else if (node.getData() instanceof ResourceGroup) {
            path = "/rhq/group/inventory/view.xhtml";
            path += ("?groupId=" + ((ResourceGroup) node.getData()).getId());
        }

        try {
            response.sendRedirect(path);
            return true; // all is well in the land
        } catch (IOException ioe) {
            FacesContext.getCurrentInstance().addMessage(
                "leftNavGroupTreeForm:leftNavGroupTree",
                new FacesMessage(FacesMessage.SEVERITY_WARN, "Unable to browse to selected group view: "
                    + ioe.getMessage(), null));
        }
        return false; // IO error from redirect
    }
}
