/*
 * Copyright (C) 2003-2008 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.ecm.webui.component.explorer;

import java.util.ArrayList;
import java.util.List;
import java.util.Set;

import javax.jcr.Node;
import javax.jcr.NodeIterator;
import javax.jcr.PathNotFoundException;
import javax.jcr.RepositoryException;
import javax.jcr.Session;
import javax.jcr.nodetype.NodeType;
import javax.jcr.nodetype.NodeTypeIterator;
import javax.jcr.nodetype.NodeTypeManager;
import javax.jcr.query.Query;
import javax.jcr.query.Row;

import org.apache.commons.lang.StringUtils;
import org.exoplatform.commons.api.search.data.SearchResult;
import org.exoplatform.ecm.jcr.model.Preference;
import org.exoplatform.ecm.webui.utils.Utils;
import org.exoplatform.services.cms.documents.DocumentTypeService;
import org.exoplatform.services.cms.link.LinkManager;
import org.exoplatform.services.cms.link.NodeFinder;
import org.exoplatform.services.cms.link.NodeLinkAware;
import org.exoplatform.services.cms.templates.TemplateService;
import org.exoplatform.services.jcr.ext.common.SessionProvider;
import org.exoplatform.services.jcr.ext.hierarchy.NodeHierarchyCreator;
import org.exoplatform.services.log.ExoLogger;
import org.exoplatform.services.log.Log;
import org.exoplatform.services.security.ConversationState;
import org.exoplatform.services.wcm.core.NodetypeConstant;
import org.exoplatform.services.wcm.search.base.LazyPageList;
import org.exoplatform.services.wcm.search.base.PageListFactory;
import org.exoplatform.services.wcm.search.base.QueryData;
import org.exoplatform.services.wcm.search.base.SearchDataCreator;
import org.exoplatform.services.wcm.utils.WCMCoreUtils;

/**
 * Created by The eXo Platform SARL
 * Author : Nguyen Anh Vu
 *          anhvurz90@gmail.com
 * Nov 9, 2009
 * 1:48:20 PM
 */
public class DocumentProviderUtils {

  private static final String   Contents_Document_Type             = "Content";
  
  private static final String FAVORITE_ALIAS = "userPrivateFavorites";
  
  private static final Log LOG  = ExoLogger.getLogger(DocumentProviderUtils.class.getName());  
  
  private static final String[] prohibitedSortType = {
                            NodetypeConstant.SORT_BY_NODESIZE,
                            NodetypeConstant.SORT_BY_NODETYPE, 
                            NodetypeConstant.SORT_BY_DATE, };
  
  private static DocumentProviderUtils docProviderUtil_ = new DocumentProviderUtils();
  private List<String> folderTypes_;
  
  private DocumentProviderUtils() {
  }
  
  public static DocumentProviderUtils getInstance() { return docProviderUtil_; }
  
  public boolean canSortType(String sortType) {
    for (String type : prohibitedSortType) {
      if (type.equals(sortType)) {
        return false;
      }
    }
    return true;
  }
  public <E> LazyPageList<E> getPageList(String ws, String path, Preference pref, 
                                                 Set<String> allItemFilter, Set<String> allItemByTypeFilter, 
                                                 SearchDataCreator<E> dataCreater) throws Exception{
    String statement = getStatement(ws, path, pref, allItemFilter, allItemByTypeFilter);
    QueryData queryData = new QueryData(statement, ws, Query.SQL, 
                                        WCMCoreUtils.getRemoteUser().equals(WCMCoreUtils.getSuperUser()));
    return PageListFactory.createLazyPageList(queryData, pref.getNodesPerPage(),dataCreater);
  }
  
  public LazyPageList<NodeLinkAware> getPageList(String ws, String path, Preference pref, 
                          Set<String> allItemFilter, Set<String> allItemByTypeFilter,
                          NodeLinkAware parent) throws Exception {
    LinkManager linkManager = WCMCoreUtils.getService(LinkManager.class);
    NodeFinder nodeFinder = WCMCoreUtils.getService(NodeFinder.class);
    String statement;
    try {
      Node node = (Node)nodeFinder.getItem(ws, path);
      if (linkManager.isLink(node)) {
        path = linkManager.getTarget(node).getPath();
      }else{
        path = node.getPath();
      }
      statement = getStatement(ws, path, pref, allItemFilter, allItemByTypeFilter);
    } catch (Exception e) {
      statement = null;
    }
    QueryData queryData = new QueryData(statement, ws, Query.SQL, 
                                        WCMCoreUtils.getRemoteUser().equals(WCMCoreUtils.getSuperUser()));
    return PageListFactory.createLazyPageList(queryData, pref.getNodesPerPage(), new NodeLinkAwareCreator(parent));
  }
  
  private String getStatement(String ws, String path, Preference pref, 
                             Set<String> allItemsFilterSet, Set<String> allItemsByTypeFilter) 
                                 throws Exception {
    StringBuilder buf = new StringBuilder();
    //path
    buf = addPathParam(buf, path);
    //jcrEnable
    buf = addJcrEnableParam(buf, ws, path, pref);
    //show non document
    buf = addShowNonDocumentType(buf, pref, allItemsByTypeFilter);
    //show hidden node
    buf = addShowHiddenNodeParam(buf, pref);
    //owned by me
    buf = addOwnedByMeParam(buf, allItemsFilterSet); 
    //favorite
    buf = addFavoriteParam(buf, ws, allItemsFilterSet);
    //all items by type
    buf = addAllItemByType(buf, allItemsByTypeFilter);
    //sort
    buf = addSortParam(buf, pref);
    return (buf == null) ? null : buf.toString();
  }
  
  /**
   * add path condition to query statement
   */
  private StringBuilder addPathParam(StringBuilder buf, String path) {
    buf.append("SELECT * FROM nt:base ");
    if (path != null) {
      if (path.endsWith("/")) {
        path = path.substring(0, path.length() - 1);
      }
      buf.append("WHERE jcr:path LIKE '").append(path)
        .append("/%' AND NOT jcr:path LIKE '").append(path).append("/%/%' ");
    }
    return buf;
  }

  /**
   * add is_jcr_enable condition to query statement
   */
  private StringBuilder addJcrEnableParam(StringBuilder buf, String ws, String path, Preference pref) 
      throws Exception {
    TemplateService templateService = WCMCoreUtils.getService(TemplateService.class);
    SessionProvider provider = WCMCoreUtils.getUserSessionProvider();
    Session session = provider.getSession(ws, WCMCoreUtils.getRepository());
    Node node = (Node)session.getItem(path);
    if(!pref.isJcrEnable() &&
        templateService.isManagedNodeType(node.getPrimaryNodeType().getName()) && 
        !(node.isNodeType(NodetypeConstant.NT_FOLDER) || node.isNodeType(NodetypeConstant.NT_UNSTRUCTURED) )) {
      return null;
    }
    return buf;
  }
  
  /**
   * add show_non_document_type condition to query statement 
   */
  private StringBuilder addShowNonDocumentType(StringBuilder buf, Preference pref, Set<String> allItemsByTypeFilter) 
      throws Exception {
    if (buf == null) return null;
    if (!pref.isShowNonDocumentType() || allItemsByTypeFilter.contains(Contents_Document_Type)) {
      if (folderTypes_ == null) {
        folderTypes_ = getFolderTypes();
      }
      buf.append(" AND (");
      //nt:unstructured && nt:folder
      buf.append("( jcr:primaryType='").append(Utils.NT_UNSTRUCTURED)
      .append("') OR (exo:primaryType='").append(Utils.NT_UNSTRUCTURED).append("') ");
      buf.append(" OR ( jcr:primaryType='").append(Utils.NT_FOLDER)
      .append("') OR (exo:primaryType='").append(Utils.NT_FOLDER).append("') ");
      //supertype of nt:unstructured or nt:folder
      for (String fType : folderTypes_) {
        buf.append(" OR ( jcr:primaryType='").append(fType)
           .append("') OR (exo:primaryType='").append(fType).append("') ");
      }
      //all document type
      TemplateService templateService = WCMCoreUtils.getService(TemplateService.class);
      List<String> docTypes = templateService.getDocumentTemplates();
      for (String docType : docTypes) {
        buf.append(" OR ( jcr:primaryType='").append(docType)
           .append("') OR (exo:primaryType='").append(docType).append("') ");
      }
      buf.append(" ) ");
    }
    return buf;
  }

  private List<String> getFolderTypes() {
    List<String> ret = new ArrayList<String>();
    NodeTypeManager nodeTypeManager = WCMCoreUtils.getRepository().getNodeTypeManager();
    try {
      for (NodeTypeIterator iter = nodeTypeManager.getAllNodeTypes(); iter.hasNext();) {
        NodeType type = iter.nextNodeType();
        if (type.isNodeType(NodetypeConstant.NT_FOLDER) || type.isNodeType(NodetypeConstant.NT_UNSTRUCTURED)) {
          ret.add(type.getName());
        }
      }
    } catch (RepositoryException e) {
      if (LOG.isWarnEnabled()) {
        LOG.warn("Can not get all node types", e.getMessage());
      }
    }
    return ret;
  }
  
  /**
   * add show_hidden_node condition to query statement 
   */
  private StringBuilder addShowHiddenNodeParam(StringBuilder buf, Preference pref) {
    if (buf == null) return null;
    if (!pref.isShowHiddenNode()) {
      buf.append(" AND ( NOT jcr:mixinTypes='").append(NodetypeConstant.EXO_HIDDENABLE).append("')");
    }
    return buf;
  }
  
  /**
   * add owned_by_me condition to query statement 
   */
  private StringBuilder addOwnedByMeParam(StringBuilder buf, Set<String> allItemsFilterSet) {
    if (buf == null) return null;
    if (allItemsFilterSet.contains(NodetypeConstant.OWNED_BY_ME)) {
      buf.append(" AND ( exo:owner='")
         .append(ConversationState.getCurrent().getIdentity().getUserId())
         .append("')");
    }
    return buf;
  }
  
  /**
   * add favorite condition to query statement 
   * @throws Exception 
   */
  private StringBuilder addFavoriteParam(StringBuilder buf, String ws, Set<String> allItemsFilterSet) 
      throws Exception {
    if (buf == null) return null;
    if (allItemsFilterSet.contains(NodetypeConstant.FAVORITE)) {
      NodeHierarchyCreator nodeHierarchyCreator = WCMCoreUtils.getService(NodeHierarchyCreator.class);
      Node userNode =
          nodeHierarchyCreator.getUserNode(WCMCoreUtils.getUserSessionProvider(), 
                                           ConversationState.getCurrent().getIdentity().getUserId());
      String favoritePath = nodeHierarchyCreator.getJcrPath(FAVORITE_ALIAS);
      int count = 0;
      buf.append(" AND (");
      for (NodeIterator iter = userNode.getNode(favoritePath).getNodes();iter.hasNext();) {
        Node node = iter.nextNode();
        if (node.isNodeType(NodetypeConstant.EXO_SYMLINK) &&
            node.hasProperty(NodetypeConstant.EXO_WORKSPACE) &&
            ws.equals(node.getProperty(NodetypeConstant.EXO_WORKSPACE).getString())) {
          if (count ++ > 0) {
            buf.append(" OR ");
          }
          buf.append(" jcr:uuid='")
             .append(node.getProperty("exo:uuid").getString())
             .append("'");
        }
      }
      buf.append(" ) ");
      if (count == 0) {
        return null;
      } 
    }
    return buf;
  }
  
  /**
   * add mimetype condition to query statement 
   */
  private StringBuilder addAllItemByType(StringBuilder buf, Set<String> allItemsByTypeFilterSet) {
    if(allItemsByTypeFilterSet.isEmpty()) {
      return buf;
    }
    DocumentTypeService documentTypeService = WCMCoreUtils.getService(DocumentTypeService.class);
    StringBuilder buf1 = new StringBuilder(" AND (");
    int count = 0;
    for (String documentType : allItemsByTypeFilterSet) {
      for (String mimeType : documentTypeService.getMimeTypes(documentType)) {
        if (count++ > 0) {
          buf1.append(" OR ");
        }
        if (mimeType.endsWith("/")) { mimeType = mimeType.substring(0, mimeType.length() - 1); }
        buf1.append(" 'jcr:content/jcr:mimeType' like '").append(mimeType).append("/%'");
      }
    }
    buf1.append(" )");
    if (count > 0) {
      buf.append(buf1);
    }
    return buf;
  }

  /**
   * adds 'sort by' condition to query statement
   */
  private StringBuilder addSortParam(StringBuilder buf, Preference pref) {
    if (buf == null) return null;
    String type = "";
    if (NodetypeConstant.SORT_BY_NODENAME.equals(pref.getSortType())) { type="exo:name"; } 
    else if (NodetypeConstant.SORT_BY_CREATED_DATE.equals(pref.getSortType())) { 
      type = NodetypeConstant.EXO_DATE_CREATED;
    } else if (NodetypeConstant.SORT_BY_MODIFIED_DATE.equals(pref.getSortType())) {
      type = NodetypeConstant.EXO_LAST_MODIFIED_DATE;
    } else { type= pref.getSortType(); }
    buf.append(" ORDER BY ").append(type).append(" ");
    buf.append("Ascending".equals(pref.getOrder()) ? "ASC" : "DESC");
    return buf;
  }
  
  /**
   * Simple data creator, just creates the node result itself
   */
  public class NodeLinkAwareCreator implements SearchDataCreator<NodeLinkAware> {

    private NodeLinkAware parent;
    
    public NodeLinkAwareCreator(NodeLinkAware parent) {
      this.parent = parent;
    }
    
    @Override
    public NodeLinkAware createData(Node node, Row row, SearchResult searchResult) {
      try {
        return (NodeLinkAware)parent.getNode(StringUtils.substringAfterLast(node.getPath(), "/"));
      } catch (PathNotFoundException e) {
        if (LOG.isWarnEnabled()) {
          LOG.warn("Can not create NodeLinkAware ", e.getMessage());
        }
      } catch (RepositoryException e) {
        if (LOG.isWarnEnabled()) {
          LOG.warn("Can not create NodeLinkAware ", e.getMessage());
        }
      }
      return null;
    }
  }
}
