/*
 * Copyright (C) 2005 Alfresco, Inc.
 *
 * Licensed under the Mozilla Public License version 1.1 
 * with a permitted attribution clause. You may obtain a
 * copy of the License at
 *
 *   http://www.alfresco.org/legal/license.txt
 *
 * Unless required by applicable law or agreed to in writing,
 * software distributed under the License is distributed on an
 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND,
 * either express or implied. See the License for the specific
 * language governing permissions and limitations under the
 * License.
 */
package org.alfresco.web.bean;

import java.text.MessageFormat;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import javax.faces.context.FacesContext;
import javax.faces.event.ActionEvent;

import org.alfresco.config.ConfigService;
import org.alfresco.error.AlfrescoRuntimeException;
import org.alfresco.filesys.CIFSServer;
import org.alfresco.filesys.server.filesys.DiskSharedDevice;
import org.alfresco.filesys.smb.server.repo.ContentContext;
import org.alfresco.filesys.smb.server.repo.ContentDiskInterface;
import org.alfresco.model.ContentModel;
import org.alfresco.service.cmr.repository.InvalidNodeRefException;
import org.alfresco.service.cmr.repository.NodeRef;
import org.alfresco.service.cmr.repository.NodeService;
import org.alfresco.service.cmr.repository.Path;
import org.alfresco.service.cmr.repository.TemplateImageResolver;
import org.alfresco.service.cmr.repository.TemplateNode;
import org.alfresco.service.cmr.search.SearchService;
import org.alfresco.service.namespace.NamespaceService;
import org.alfresco.web.app.AlfrescoNavigationHandler;
import org.alfresco.web.app.Application;
import org.alfresco.web.app.context.UIContextService;
import org.alfresco.web.bean.repository.Node;
import org.alfresco.web.bean.repository.Repository;
import org.alfresco.web.bean.repository.User;
import org.alfresco.web.bean.wizard.NewSpaceWizard;
import org.alfresco.web.config.ClientConfigElement;
import org.alfresco.web.ui.common.Utils;
import org.alfresco.web.ui.common.component.IBreadcrumbHandler;
import org.alfresco.web.ui.common.component.UIBreadcrumb;
import org.alfresco.web.ui.common.component.UIModeList;
import org.alfresco.web.ui.repo.component.IRepoBreadcrumbHandler;
import org.alfresco.web.ui.repo.component.shelf.UIShelf;
import org.apache.log4j.Logger;

/**
 * @author Kevin Roast
 */
public class NavigationBean
{
   // ------------------------------------------------------------------------------
   // Bean property getters and setters 

   /**
    * @param nodeService The nodeService to set.
    */
   public void setNodeService(NodeService nodeService)
   {
      this.nodeService = nodeService;
   }

   /**
    * @param searchService The searchService to set.
    */
   public void setSearchService(SearchService searchService)
   {
      this.searchService = searchService;
   }
   
   /**
    * @param namespaceService The namespaceService to set.
    */
   public void setNamespaceService(NamespaceService namespaceService)
   {
      this.namespaceService = namespaceService;
   }
   
   /**
    * @param cifsServer The cifsServer to set.
    */
   public void setCifsServer(CIFSServer cifsServer)
   {
      this.cifsServer = cifsServer;
   }

   /**
    * @param contentDiskDriver The contentDiskDriver to set.
    */
   public void setContentDiskDriver(ContentDiskInterface contentDiskDriver)
   {
      this.contentDiskDriver = contentDiskDriver;
   }
   
   /**
    * @param configService The ConfigService to set.
    */
   public void setConfigService(ConfigService configService)
   {
      this.configService = configService;
   }
   
   /**
    * @return the User object representing the current instance for this user 
    */
   public User getCurrentUser()
   {
      return Application.getCurrentUser(FacesContext.getCurrentInstance());
   }

   /**
    * Return the expanded state of the Shelf panel wrapper component
    * 
    * @return the expanded state of the Shelf panel wrapper component
    */
   public boolean isShelfExpanded()
   {
      return this.shelfExpanded;
   }
   
   /**
    * Set the expanded state of the Shelf panel wrapper component
    * 
    * @param expanded      true to expanded the Shelf panel area, false to hide it
    */
   public void setShelfExpanded(boolean expanded)
   {
      this.shelfExpanded = expanded;
   }
   
   /**
    * @return Returns the array containing the expanded state of the shelf items
    */
   public boolean[] getShelfItemExpanded()
   {
      return this.shelfItemExpanded;
   }

   /**
    * @param shelfItemExpanded The array containing the expanded state of the shelf items
    */
   public void setShelfItemExpanded(boolean[] shelfItemExpanded)
   {
      this.shelfItemExpanded = shelfItemExpanded;
   }
   
   /**
    * @return Returns the toolbar Location.
    */
   public String getToolbarLocation()
   {
      return this.toolbarLocation;
   }
   
   /**
    * @param toolbarLocation  The toolbar Location to set.
    */
   public void setToolbarLocation(String toolbarLocation)
   {
      this.toolbarLocation = toolbarLocation;
   }
   
   /**
    * @return Returns the helpUrl.
    */
   public String getHelpUrl()
   {
      if (this.clientConfig == null)
      {
         initFromClientConfig();
      }
      return this.helpUrl;
   }

   /**
    * @param helpUrl The helpUrl to set.
    */
   public void setHelpUrl(String helpUrl)
   {
      this.helpUrl = helpUrl;
   }
   
   /**
    * @return Returns the search context object if any.
    */
   public SearchContext getSearchContext()
   {
      return this.searchContext;
   }
   
   /**
    * @param searchContext    The search context object to set or null to clear search.
    */
   public void setSearchContext(SearchContext searchContext)
   {
      this.searchContext = searchContext;
      
      UIContextService.getInstance(FacesContext.getCurrentInstance()).notifyBeans();
   }
   
   /**
    * @return Returns the currently browsing node Id.
    */
   public String getCurrentNodeId()
   {
      return this.currentNodeId;
   }

   /**
    * Set the node Id of the current folder/space container node.
    * <p>
    * Setting this value causes the UI to update and display the specified node as current.
    * 
    * @param currentNodeId    The currently browsing node Id.
    */
   public void setCurrentNodeId(String currentNodeId)
   {
      if (s_logger.isDebugEnabled())
         s_logger.debug("Setting current node id to: " + currentNodeId);
      
      if (currentNodeId == null)
      {
         throw new AlfrescoRuntimeException("Can not set the current node id to null");
      }
      
      // set the current Node Id for our UI context operations
      this.currentNodeId = currentNodeId;
      
      // clear other context that is based on or relevant to the Node id
      this.currentNode = null;
      this.searchContext = null;
      
      // inform any interested beans that the UI needs updating after this change 
      UIContextService.getInstance(FacesContext.getCurrentInstance()).notifyBeans();
      
      // clear current node context after the notify - this is to ensure that if any delegates
      // performed operations on the current node, that we have fresh data for the next View
      this.currentNode = null;
   }
   
   /**
    * @return true if the current node has a template view available
    */
   public boolean getCurrentNodeHasTemplate()
   {
      boolean templateView = false;
      Node node = getCurrentNode();
      if (node.hasAspect(ContentModel.ASPECT_TEMPLATABLE))
      {
         NodeRef templateRef = (NodeRef)node.getProperties().get(ContentModel.PROP_TEMPLATE);
         templateView = (templateRef != null && this.nodeService.exists(templateRef));
      }
      return templateView;
   }
   
   /**
    * @return the NodeRef.toString() for the current node template view if it has one set 
    */
   public String getCurrentNodeTemplate()
   {
      String strRef = null;
      if (getCurrentNodeHasTemplate() == true)
      {
         strRef = getCurrentNode().getProperties().get(ContentModel.PROP_TEMPLATE).toString();
      }
      return strRef;
   }
   
   /**
    * Returns a model for use by a template on a space Dashboard page.
    * 
    * @return model containing current current space info.
    */
   public Map getTemplateModel()
   {
      HashMap model = new HashMap(1, 1.0f);
      
      FacesContext fc = FacesContext.getCurrentInstance();
      TemplateNode spaceNode = new TemplateNode(getCurrentNode().getNodeRef(), Repository.getServiceRegistry(fc),
            new TemplateImageResolver() {
               public String resolveImagePathForName(String filename, boolean small) {
                  return Utils.getFileTypeImage(filename, small);
               }
            });
      model.put("space", spaceNode);
      
      return model;
   }
   
   /**
    * Clear state so that the current node properties cache for the next time they are requested
    */
   public void resetCurrentNodeProperties()
   {
      this.currentNode = null;
   }
   
   /**
    * @return The Map of properties for the current Node. 
    */
   public Map<String, Object> getNodeProperties()
   {
      return getCurrentNode().getProperties();
   }
   
   /**
    * @return The current Node object for UI context operations
    */
   public Node getCurrentNode()
   {
      if (this.currentNode == null)
      {
         if (this.currentNodeId == null)
         {
            throw new AlfrescoRuntimeException("Cannot retrieve current Node if NodeId is null!");
         }
         
         if (s_logger.isDebugEnabled())
            s_logger.debug("Caching properties for node id: " + this.currentNodeId);
         
         NodeRef nodeRef;
         Node node;
         Map<String, Object> props;
         try
         {
            // build a node which components on the JSP page can bind too
            nodeRef = new NodeRef(Repository.getStoreRef(), this.currentNodeId);
            node = new Node(nodeRef);
            
            // early init properties for this node (by getProperties() call)
            // resolve icon in-case one has not been set
            props = node.getProperties();
         }
         catch (InvalidNodeRefException refErr)
         {
            Utils.addErrorMessage(MessageFormat.format(Application.getMessage(
                  FacesContext.getCurrentInstance(), ERROR_DELETED_FOLDER), new Object[] {this.currentNodeId}) );
            
            nodeRef = new NodeRef(Repository.getStoreRef(), Application.getCompanyRootId());
            node = new Node(nodeRef);
            props = node.getProperties();
         }
         String icon = (String)props.get("app:icon");
         props.put("icon", icon != null ? icon : NewSpaceWizard.SPACE_ICON_DEFAULT);
         Path path = this.nodeService.getPath(nodeRef);
         
         // resolve CIFS network folder location for this node
         
         DiskSharedDevice diskShare = cifsServer.getConfiguration().getPrimaryFilesystem();
         
         if (diskShare != null)
         {
             ContentContext contentCtx = (ContentContext) diskShare.getContext();
             NodeRef rootNode = contentCtx.getRootNode();
             String cifsPath = Repository.getNamePath(this.nodeService, path, rootNode, "\\", "file:///" + getCIFSServerPath(diskShare));
              
             node.getProperties().put("cifsPath", cifsPath);
             node.getProperties().put("cifsPathLabel", cifsPath.substring(8));  // strip file:/// part
         }
         
         this.currentNode = node;
      }
      
      return this.currentNode;
   }

   /**
    * @return Returns the breadcrumb handler elements representing the location path of the UI.
    */
   public List<IBreadcrumbHandler> getLocation()
   {
      if (this.location == null)
      {
         // init the location from the User object for the first time
         User user = Application.getCurrentUser(FacesContext.getCurrentInstance());
         
         NodeRef homeSpaceRef = new NodeRef(Repository.getStoreRef(), user.getHomeSpaceId());
         String homeSpaceName = Repository.getNameForNode(this.nodeService, homeSpaceRef);
         
         // set the current node to the users Home Space Id
         setCurrentNodeId(user.getHomeSpaceId());
         
         // setup the breadcrumb with the same location
         List<IBreadcrumbHandler> elements = new ArrayList<IBreadcrumbHandler>(1);
         elements.add(new NavigationBreadcrumbHandler(homeSpaceRef, homeSpaceName));
         setLocation(elements);
      }
      
      return this.location;
   }
   
   /**
    * @param location      The UI location representation to set.
    */
   public void setLocation(List<IBreadcrumbHandler> location)
   {
      this.location = location;
   }
   
   /**
    * Sets up the dispatch context so that the navigation handler knows
    * what object is being acted upon
    * 
    * @param node The node to be added to the dispatch context
    */
   public void setupDispatchContext(Node node)
   {
      this.dispatchContext = node;
   }
   
   /**
    * Resets the dispatch context
    */
   public void resetDispatchContext()
   {
      this.dispatchContext = null;
   }
   
   /**
    * Returns the node currently set in the dispatch context
    * 
    * @return The node being dispatched or null if there is no 
    *         dispatch context
    */
   public Node getDispatchContextNode()
   {
      return this.dispatchContext;
   }
   
   
   // ------------------------------------------------------------------------------
   // Navigation action event handlers
   
   /**
    * Action handler to toggle the expanded state of the shelf.
    * The panel component wrapping the shelf area of the UI is value bound to the shelfExpanded property.
    */
   public void toggleShelf(ActionEvent event)
   {
      this.shelfExpanded = !this.shelfExpanded;
   }
   
   /**
    * Action handler called after a Shelf Group has had its expanded state toggled by the user
    */
   public void shelfGroupToggled(ActionEvent event)
   {
      UIShelf.ShelfEvent shelfEvent = (UIShelf.ShelfEvent)event;
      this.shelfItemExpanded[shelfEvent.Index] = shelfEvent.Expanded;
   }
   
   /**
    * Action to change the toolbar location
    * Currently this will changed the location from Company to the users Home space
    */
   public void toolbarLocationChanged(ActionEvent event)
   {
      FacesContext context = FacesContext.getCurrentInstance();
      try
      {
         UIModeList locationList = (UIModeList)event.getComponent();
         String location = locationList.getValue().toString();
         setToolbarLocation(location);
         
         if (LOCATION_COMPANY.equals(location))
         {
            List<IBreadcrumbHandler> elements = new ArrayList<IBreadcrumbHandler>(1);
            NodeRef companyRootRef = new NodeRef(Repository.getStoreRef(), Application.getCompanyRootId());
            String companySpaceName = Repository.getNameForNode(this.nodeService, companyRootRef);
            elements.add(new NavigationBreadcrumbHandler(companyRootRef, companySpaceName));
            setLocation(elements);
            setCurrentNodeId(companyRootRef.getId());
         }
         else if (LOCATION_HOME.equals(location))
         {
            List<IBreadcrumbHandler> elements = new ArrayList<IBreadcrumbHandler>(1);
            String homeSpaceId = Application.getCurrentUser(context).getHomeSpaceId();
            NodeRef homeSpaceRef = new NodeRef(Repository.getStoreRef(), homeSpaceId);
            String homeSpaceName = Repository.getNameForNode(this.nodeService, homeSpaceRef);
            elements.add(new NavigationBreadcrumbHandler(homeSpaceRef, homeSpaceName));
            setLocation(elements);
            setCurrentNodeId(homeSpaceRef.getId());
         }
         
         // we need to force a navigation to refresh the browse screen breadcrumb
         context.getApplication().getNavigationHandler().handleNavigation(context, null, "browse");
      }
      catch (InvalidNodeRefException refErr)
      {
         Utils.addErrorMessage(MessageFormat.format(Application.getMessage(
               FacesContext.getCurrentInstance(), Repository.ERROR_NOHOME), Application.getCurrentUser(context).getHomeSpaceId()), refErr );
      }
      catch (Exception err)
      {
         Utils.addErrorMessage(MessageFormat.format(Application.getMessage(
               FacesContext.getCurrentInstance(), Repository.ERROR_GENERIC), err.getMessage()), err);
      }
   }
   
   /**
    * @param diskShare Filesystem shared device
    * @return CIFS server path as network style string label
    */
   public String getCIFSServerPath(DiskSharedDevice diskShare)
   {
      if (this.cifsServerPath == null)
      {
         StringBuilder buf = new StringBuilder(24);
         
         String serverName = this.cifsServer.getConfiguration().getServerName();
         if (serverName != null && serverName.length() != 0)
         {
            buf.append("\\\\")
               .append(serverName)
               .append("\\");
            buf.append(diskShare.getName());
         }
         
         this.cifsServerPath = buf.toString();
      }
      
      return this.cifsServerPath;
   }
   
   // ------------------------------------------------------------------------------
   // Private helpers
   
   /**
    * Initialise default values from client configuration
    */
   private void initFromClientConfig()
   {
      this.clientConfig = (ClientConfigElement)this.configService.getGlobalConfig().getConfigElement(
            ClientConfigElement.CONFIG_ELEMENT_ID);
      
      this.helpUrl = clientConfig.getHelpUrl();
   }
   
   // ------------------------------------------------------------------------------
   // Inner classes
   
   /**
    * Class to handle breadcrumb interaction for top-level navigation pages
    */
   public class NavigationBreadcrumbHandler implements IRepoBreadcrumbHandler
   {
      private static final long serialVersionUID = 4833194653193016638L;
      
      /**
       * Constructor
       * 
       * @param label      Element label
       */
      public NavigationBreadcrumbHandler(NodeRef ref, String label)
      {
         this.label = label;
         this.ref = ref;
      }
      
      /**
       * @see java.lang.Object#toString()
       */
      public String toString()
      {
         return this.label;
      }

      /**
       * @see org.alfresco.web.ui.common.component.IBreadcrumbHandler#navigationOutcome(org.alfresco.web.ui.common.component.UIBreadcrumb)
       */
      public String navigationOutcome(UIBreadcrumb breadcrumb)
      {
         // set the current node to the specified top level node ID
         FacesContext fc = FacesContext.getCurrentInstance();
         setCurrentNodeId(ref.getId());
         setLocation( (List)breadcrumb.getValue() );
         
         // setup the dispatch context
         setupDispatchContext(new Node(ref));
         
         if (fc.getViewRoot().getViewId().equals(BrowseBean.BROWSE_VIEW_ID))
         {
            return null;
         }
         else
         {
            return "browse";
         }
      }
      
      public NodeRef getNodeRef()
      {
         return this.ref;
      }
      
      private String label;
      private NodeRef ref;
   }

   
   // ------------------------------------------------------------------------------
   // Private data
   
   private static Logger s_logger = Logger.getLogger(NavigationBean.class);
   
   /** constant values used by the toolbar location modelist control */
   private static final String LOCATION_COMPANY = "company";
   private static final String LOCATION_HOME = "home";
   
   private static final String ERROR_DELETED_FOLDER = "error_deleted_folder";
   
   /** The NodeService to be used by the bean */
   private NodeService nodeService;
   
   /** The SearchService to be used by the bean */
   private SearchService searchService;
   
   /** NamespaceService bean reference */
   private NamespaceService namespaceService;
   
   /** CIFSServer bean reference */
   private CIFSServer cifsServer;
   
   /** CIFS content disk driver bean reference */
   private ContentDiskInterface contentDiskDriver;
   
   /** ConfigService bean reference */
   private ConfigService configService;
   
   /** Client configuration object */
   private ClientConfigElement clientConfig = null;
   
   /** Cached path to our CIFS server and top level node DIR */
   private String cifsServerPath;
   
   /** Node Id we are using for UI context operations */
   private String currentNodeId;
   
   /** Node we are using for UI context operations */
   private Node currentNode = null;
   
   /** Node we are using for dispatching */
   private Node dispatchContext = null;
   
   /** Current toolbar location */
   private String toolbarLocation = LOCATION_HOME;
   
   /** Search context object we are currently using or null for no search */
   private SearchContext searchContext;
   
   /** expanded state of the Shelf panel wrapper component */
   private boolean shelfExpanded = true;
   
   /** expanded state of the Shelf item components */
   private boolean[] shelfItemExpanded = new boolean[] {true, true, true, false, false};
   
   /** list of the breadcrumb handler elements representing the location path of the UI */
   private List<IBreadcrumbHandler> location = null;
   
   /** The client Help file url */
   private String helpUrl;
}
