package org.exoplatform.services.wcm.search.connector;

import org.apache.commons.chain.Context;
import org.exoplatform.commons.search.index.IndexingService;
import org.exoplatform.commons.utils.CommonsUtils;
import org.exoplatform.services.cms.documents.TrashService;
import org.exoplatform.services.ext.action.InvocationContext;
import org.exoplatform.services.jcr.impl.core.NodeImpl;
import org.exoplatform.services.jcr.impl.core.PropertyImpl;
import org.exoplatform.services.jcr.impl.ext.action.AdvancedAction;
import org.exoplatform.services.jcr.impl.ext.action.AdvancedActionException;
import org.exoplatform.services.jcr.observation.ExtendedEvent;
import org.exoplatform.services.log.ExoLogger;
import org.exoplatform.services.log.Log;
import org.exoplatform.services.wcm.core.NodeLocation;
import org.exoplatform.services.wcm.core.NodetypeConstant;
import org.exoplatform.services.wcm.utils.WCMCoreUtils;

import javax.jcr.Node;
import javax.jcr.NodeIterator;
import javax.jcr.RepositoryException;
import javax.jcr.observation.Event;
import java.util.function.Consumer;
import java.util.function.Predicate;

/**
 *  JCR action which listens on all nodes events to index them
 */
public class FileIndexerAction implements AdvancedAction {
  private static final Log LOGGER = ExoLogger.getExoLogger(FileIndexerAction.class);

  private IndexingService indexingService;

  private TrashService trashService;

  public FileIndexerAction() {
    this.indexingService = CommonsUtils.getService(IndexingService.class);
    this.trashService = CommonsUtils.getService(TrashService.class);
  }

  @Override
  public boolean execute(Context context) throws Exception {
    NodeImpl node;

    int eventType = (Integer) context.get(InvocationContext.EVENT);

    switch(eventType) {
      case Event.NODE_ADDED:
        node = (NodeImpl)context.get(InvocationContext.CURRENT_ITEM);
        if(node != null) {
          if (trashService.isInTrash(node)) {
            applyIndexingOperationOnNodes(node, n -> indexingService.unindex(FileindexingConnector.TYPE, n.getInternalIdentifier()), n -> true);
          } else {
            applyIndexingOperationOnNodes(node, n -> indexingService.index(FileindexingConnector.TYPE, n.getInternalIdentifier()), n -> true);
          }
        }
        break;
      case Event.NODE_REMOVED:
        node = (NodeImpl)context.get(InvocationContext.CURRENT_ITEM);
        if(node != null) {
          applyIndexingOperationOnNodes(node, n -> indexingService.unindex(FileindexingConnector.TYPE, n.getInternalIdentifier()), n -> true);
        }
        break;
      case Event.PROPERTY_ADDED:
      case Event.PROPERTY_CHANGED:
      case Event.PROPERTY_REMOVED:
        PropertyImpl property = (PropertyImpl) context.get(InvocationContext.CURRENT_ITEM);
        if(property != null) {
          node = property.getParent();
          if (node != null && !trashService.isInTrash(node)) {
            if (node.isNodeType(NodetypeConstant.NT_RESOURCE)) {
              node = node.getParent();
            }
            if (node.isNodeType(NodetypeConstant.NT_FILE)) {
              indexingService.reindex(FileindexingConnector.TYPE, node.getInternalIdentifier());
            }
          }
        }
        break;
      case ExtendedEvent.PERMISSION_CHANGED:
        node = (NodeImpl)context.get(InvocationContext.CURRENT_ITEM);
        if (node != null && !trashService.isInTrash(node)) {
          // reindex children nodes when permissions has been changed (exo:permissions) - it is required
          // to update permissions of the nodes in the indexing engine
          applyIndexingOperationOnNodes(node, n -> indexingService.reindex(FileindexingConnector.TYPE, n.getInternalIdentifier()), n -> hasNotPrivilegeableMixin(n));
        }
        break;
      case ExtendedEvent.NODE_MOVED:
        node = (NodeImpl) context.get(InvocationContext.CURRENT_ITEM);
        if (node != null && !trashService.isInTrash(node)) {
          applyIndexingOperationOnNodes(node, n -> indexingService.reindex(FileindexingConnector.TYPE, n.getInternalIdentifier()), n -> true);
        }
        break;
    }

    return true;
  }

  @Override
  public void onError(Exception e, Context context) throws AdvancedActionException {
    LOGGER.error("Error while indexing file", e);
  }

  protected Node getNodeByPath(String path) {
    return NodeLocation.getNodeByLocation(new NodeLocation(WCMCoreUtils.getRepository().getConfiguration().getName(), "collaboration", path));
  }

  /**
   * Apply the given indexing operation (index|reindex|unindex) on all children of a node, only for nt:file nodes
   * @param node The root node to operate on
   * @param filter skip process node if filter return true
   * @param indexingOperation Indexing operation (index|reindex|unindex) to apply on the nodes
   */
  protected void applyIndexingOperationOnNodes(NodeImpl node, Consumer<NodeImpl> indexingOperation, Predicate<NodeImpl> filter) {
    if (node == null) {
      return;
    }

    try {
      if (node.isNodeType(NodetypeConstant.NT_FILE)) {
        indexingOperation.accept(node);
      }
    } catch (RepositoryException e) {
      LOGGER.error("Cannot get primary type of node " + node.getInternalIdentifier(), e);
    }

    try {
      NodeIterator nodeIterator = node.getNodes();
      while(nodeIterator.hasNext()) {
        NodeImpl childNode = (NodeImpl) nodeIterator.nextNode();
        if(! filter.test(childNode))
          continue;
        applyIndexingOperationOnNodes(childNode, indexingOperation, filter);
      }
    } catch (RepositoryException e) {
      LOGGER.error("Cannot get child nodes of node " + node.getInternalIdentifier(), e);
    }
  }
  // Check if the node has exo:privilegeable mixin
  private boolean hasNotPrivilegeableMixin(NodeImpl node) {
    try {
      return ! node.isNodeType(NodetypeConstant.EXO_PRIVILEGEABLE);
    } catch (RepositoryException e) {
      LOGGER.error("Error while check privilegeable mixin ", e);
    }
    return true;
  }
}
