/*
 * Copyright (C) 2003-2009 eXo Platform SAS.
 *
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Affero General Public License
 * as published by the Free Software Foundation; either version 3
 * of the License, or (at your option) any later version.
 *
 * 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, see<http://www.gnu.org/licenses/>.
 */
package org.exoplatform.wcm.webui.category;

import java.io.UnsupportedEncodingException;
import java.net.URLDecoder;
import java.util.ArrayList;
import java.util.List;

import javax.jcr.Node;
import javax.jcr.NodeIterator;
import javax.jcr.RepositoryException;
import javax.jcr.nodetype.NodeType;
import javax.portlet.PortletPreferences;

import org.apache.commons.lang.StringUtils;
import org.exoplatform.ecm.webui.utils.Utils;
import org.exoplatform.portal.webui.container.UIContainer;
import org.exoplatform.portal.webui.util.Util;
import org.exoplatform.services.cms.link.NodeFinder;
import org.exoplatform.services.cms.taxonomy.TaxonomyService;
import org.exoplatform.services.ecm.publication.PublicationService;
import org.exoplatform.services.log.ExoLogger;
import org.exoplatform.services.log.Log;
import org.exoplatform.services.wcm.core.NodeLocation;
import org.exoplatform.wcm.webui.category.config.UICategoryNavigationConfig;
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.EventConfig;
import org.exoplatform.webui.core.UITree;
import org.exoplatform.webui.core.lifecycle.Lifecycle;
import org.exoplatform.webui.event.Event;
import org.exoplatform.webui.event.EventListener;

/**
 * Created by The eXo Platform SAS
 * Author : eXoPlatform
 * chuong.phan@exoplatform.com, phan.le.thanh.chuong@gmail.com
 * Jun 19, 2009
 */
@ComponentConfig(
    lifecycle = Lifecycle.class,
    template = "app:/groovy/CategoryNavigation/UICategoryNavigationTree.gtmpl",
    events = {
      @EventConfig(listeners = UICategoryNavigationTree.QuickEditActionListener.class),
      @EventConfig(listeners = UICategoryNavigationTree.ChangeNodeActionListener.class)
    }
)
public class UICategoryNavigationTree extends UIContainer {

  private static final Log         LOG             = ExoLogger.getLogger(UICategoryNavigationTree.class.getName());
  
  /** The allow publish. */
  private boolean            allowPublish        = false;

  /** The publication service_. */
  private PublicationService publicationService_ = null;

  /** The templates_. */
  private List<String>       templates_          = null;

  /** The accepted node types. */
  private String[]           acceptedNodeTypes   = {};

  /** The root tree node. */
  protected NodeLocation rootTreeNode;

  /** The current node. */
  protected NodeLocation currentNode;

  /**
   * Checks if is allow publish.
   *
   * @return true, if is allow publish
   */
  public boolean isAllowPublish() {
    return allowPublish;
  }

  /**
   * Sets the allow publish.
   *
   * @param allowPublish the allow publish
   * @param publicationService the publication service
   * @param templates the templates
   */
  public void setAllowPublish(boolean allowPublish,
                              PublicationService publicationService,
                              List<String> templates) {
    this.allowPublish = allowPublish;
    publicationService_ = publicationService;
    templates_ = templates;
  }

  /**
   * Instantiates a new uI node tree builder.
   *
   * @throws Exception the exception
   */
  public UICategoryNavigationTree() throws Exception {

    PortletPreferences portletPreferences = UICategoryNavigationUtils.getPortletPreferences();
    String preferenceTreeName = portletPreferences.getValue(UICategoryNavigationConstant.PREFERENCE_TREE_NAME, "");
    TaxonomyService taxonomyService = getApplicationComponent(TaxonomyService.class);
    Node rootTreeNode = null;
    try {
      rootTreeNode = taxonomyService.getTaxonomyTree(preferenceTreeName);
    } catch (RepositoryException e) {
      if (LOG.isWarnEnabled()) {
        LOG.warn(e.getMessage());
      }
    }
    setRootTreeNode(rootTreeNode);
    setAcceptedNodeTypes(new String[] {"nt:folder", "nt:unstructured", "nt:file", "exo:taxonomy"});

    UITree tree = addChild(UICategoryNavigationTreeBase.class, null, null);
    tree.setBeanLabelField("name");
    tree.setBeanIdField("path");
  }

  /**
   * Gets the root tree node.
   *
   * @return the root tree node
   */
  public Node getRootTreeNode() {
    return NodeLocation.getNodeByLocation(rootTreeNode);
  }

  /**
   * Sets the root tree node.
   *
   * @param node the new root tree node
   *
   * @throws Exception the exception
   */
  public final void setRootTreeNode(Node node) throws Exception {
    this.rootTreeNode = NodeLocation.getNodeLocationByNode(node);
    this.currentNode = NodeLocation.getNodeLocationByNode(node);
  }

  /**
   * Gets the current node.
   *
   * @return the current node
   */
  public Node getCurrentNode() {
    return NodeLocation.getNodeByLocation(currentNode);
  }

  /**
   * Sets the current node.
   *
   * @param currentNode the new current node
   */
  public void setCurrentNode(Node currentNode) {
    this.currentNode = NodeLocation.getNodeLocationByNode(currentNode);
  }

  /**
   * Gets the accepted node types.
   *
   * @return the accepted node types
   */
  public String[] getAcceptedNodeTypes() {
    return acceptedNodeTypes;
  }

  /**
   * Sets the accepted node types.
   *
   * @param acceptedNodeTypes the new accepted node types
   */
  public void setAcceptedNodeTypes(String[] acceptedNodeTypes) {
    this.acceptedNodeTypes = acceptedNodeTypes;
  }

  /* (non-Javadoc)
   * @see org.exoplatform.webui.core.UIComponent#processRender(org.exoplatform.webui.application.WebuiRequestContext)
   */
  public void processRender(WebuiRequestContext context) throws Exception {
    String parameters = null;
    try {
      parameters = URLDecoder.decode(StringUtils.substringAfter(Util.getPortalRequestContext()
                                                                    .getNodePath(),
                                                                Util.getUIPortal()
                                                                    .getSelectedUserNode()
                                                                    .getURI()
                                                                    + "/"), "UTF-8");
    } catch (UnsupportedEncodingException e) {
      org.exoplatform.wcm.webui.Utils.createPopupMessage(this,
                                                         "UICategoryNavigationConfig.msg.not-support-encoding",
                                                         null,
                                                         ApplicationMessage.ERROR);
    }
    PortletPreferences portletPreferences = UICategoryNavigationUtils.getPortletPreferences();
    String preferenceTreeName = portletPreferences.getValue(UICategoryNavigationConstant.PREFERENCE_TREE_NAME, "");
    TaxonomyService taxonomyService = getApplicationComponent(TaxonomyService.class);
    Node treeNode = null;
    try {
      treeNode = taxonomyService.getTaxonomyTree(preferenceTreeName);
    } catch (RepositoryException e) {
      currentNode = null;
      super.processRender(context);
      return;
    }


    String categoryPath = parameters.substring(parameters.indexOf("/") + 1);
    if (preferenceTreeName.equals(categoryPath)) categoryPath = "";
    try {
      currentNode = NodeLocation.getNodeLocationByNode(treeNode.getNode(categoryPath));
    } catch (Exception e) {
      if (LOG.isWarnEnabled()) {
        LOG.warn(e.getMessage());
      }
    }
    super.processRender(context);
  }

  /**
   * Builds the tree.
   *
   * @throws Exception the exception
   */
  public void buildTree() throws Exception {
    NodeIterator sibbling = null;
    NodeIterator children = null;
    UICategoryNavigationTreeBase tree = getChild(UICategoryNavigationTreeBase.class);
    Node selectedNode = NodeLocation.getNodeByLocation(currentNode);
    tree.setSelected(selectedNode);
    if (selectedNode == null) {
      return;
    }
    if (Utils.getNodeSymLink(selectedNode).getDepth() > 0) {
      tree.setParentSelected(selectedNode.getParent());
      sibbling = Utils.getNodeSymLink(selectedNode).getNodes();
      children = Utils.getNodeSymLink(selectedNode).getNodes();
    } else {
      tree.setParentSelected(selectedNode);
      sibbling = Utils.getNodeSymLink(selectedNode).getNodes();
      children = null;
    }
    if (sibbling != null) {
      tree.setSibbling(filter(sibbling));
    }
    if (children != null) {
      tree.setChildren(filter(children));
    }
  }

  /**
   * Adds the node publish.
   *
   * @param listNode the list node
   * @param node the node
   * @param publicationService the publication service
   *
   * @throws Exception the exception
   */
  private void addNodePublish(List<Node> listNode, Node node, PublicationService publicationService) throws Exception {
    if (isAllowPublish()) {
      NodeType nt = node.getPrimaryNodeType();
      if (templates_.contains(nt.getName())) {
        Node nodecheck = publicationService.getNodePublish(node, null);
        if (nodecheck != null) {
          listNode.add(nodecheck);
        }
      } else {
        listNode.add(node);
      }
    } else {
      listNode.add(node);
    }
  }

  /**
   * Filter.
   *
   * @param iterator the iterator
   *
   * @return the list< node>
   *
   * @throws Exception the exception
   */
  private List<Node> filter(final NodeIterator iterator) throws Exception {
    List<Node> list = new ArrayList<Node>();
    if (acceptedNodeTypes.length > 0) {
      for (; iterator.hasNext();) {
        Node sibbling = iterator.nextNode();
        if (sibbling.isNodeType("exo:hiddenable"))
          continue;
        for (String nodetype : acceptedNodeTypes) {
          if (sibbling.isNodeType(nodetype)) {
            list.add(sibbling);
            break;
          }
        }
      }
      List<Node> listNodeCheck = new ArrayList<Node>();
      for (Node node : list) {
        addNodePublish(listNodeCheck, node, publicationService_);
      }
      return listNodeCheck;
    }
    for (; iterator.hasNext();) {
      Node sibbling = iterator.nextNode();
      if (sibbling.isNodeType("exo:hiddenable"))
        continue;
      list.add(sibbling);
    }
    List<Node> listNodeCheck = new ArrayList<Node>();
    for (Node node : list)
      addNodePublish(listNodeCheck, node, publicationService_);
    return listNodeCheck;
  }

  /**
   * When a node is change in tree. This method will be rerender the children & sibbling nodes of
   * current node and broadcast change node event to other uicomponent
   *
   * @param path the path
   * @param context the context
   *
   * @throws Exception the exception
   */
  public void changeNode(String path, Object context) throws Exception {
    NodeFinder nodeFinder_ = getApplicationComponent(NodeFinder.class);
    String rootPath = rootTreeNode.getPath();
    if (rootPath.equals(path) || !path.startsWith(rootPath)) {
      currentNode = rootTreeNode;
    } else {
      if (path.startsWith(rootPath))
        path = path.substring(rootPath.length());
      if (path.startsWith("/"))
        path = path.substring(1);
      currentNode = NodeLocation.getNodeLocationByNode(nodeFinder_.getNode(NodeLocation.getNodeByLocation(rootTreeNode), path));
    }
  }

  /**
   * The listener interface for receiving changeNodeAction events. The class
   * that is interested in processing a changeNodeAction event implements this
   * interface, and the object created with that class is registered with a
   * component using the component's
   * <code>addChangeNodeActionListener<code> method. When
   * the changeNodeAction event occurs, that object's appropriate
   * method is invoked.
   *
   * @see ChangeNodeActionEvent
   */
  static public class ChangeNodeActionListener extends EventListener<UITree> {

    /* (non-Javadoc)
     * @see org.exoplatform.webui.event.EventListener#execute(org.exoplatform.webui.event.Event)
     */
    public void execute(Event<UITree> event) throws Exception {
      UICategoryNavigationTree categoryNavigationTree = event.getSource().getParent();
      String uri = event.getRequestContext().getRequestParameter(OBJECTID);
      categoryNavigationTree.changeNode(uri, event.getRequestContext());
      event.getRequestContext().addUIComponentToUpdateByAjax(categoryNavigationTree.getParent());
    }
  }

  /**
   * The listener interface for receiving quickEditAction events. The class
   * that is interested in processing a quickEditAction event implements this
   * interface, and the object created with that class is registered with a
   * component using the component's
   * <code>addQuickEditActionListener<code> method. When
   * the quickEditAction event occurs, that object's appropriate
   * method is invoked.
   *
   * @see QuickEditActionEvent
   */
  public static class QuickEditActionListener extends EventListener<UICategoryNavigationTree> {
    /*
     * (non-Javadoc)
     *
     * @see org.exoplatform.webui.event.EventListener#execute(org.exoplatform.webui.event.Event)
     */
    public void execute(Event<UICategoryNavigationTree> event) throws Exception {
      UICategoryNavigationTree uiContainer = event.getSource();
      UICategoryNavigationConfig configForm = uiContainer.createUIComponent(UICategoryNavigationConfig.class,
                                                                            null,
                                                                            null);
      org.exoplatform.wcm.webui.Utils.createPopupWindow(uiContainer,
                                                        configForm,
                                                        UICategoryNavigationPortlet.CONFIG_POPUP_WINDOW,
                                                        600);
    }
  }

}
