/*
 * Copyright 2000-2017 JetBrains s.r.o.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * 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 com.intellij.openapi.roots;

import com.intellij.openapi.module.Module;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.roots.libraries.Library;
import com.intellij.openapi.util.Condition;
import com.intellij.openapi.vfs.VirtualFile;
import com.intellij.util.NotNullFunction;
import com.intellij.util.PathsList;
import com.intellij.util.Processor;
import org.jetbrains.annotations.NotNull;

import java.util.List;

/**
 * <p>Interface for convenient processing dependencies of a module or a project. Allows to process {@link OrderEntry}s
 * and collect classes and source roots.</p>
 *
 * <p>Use {@link #orderEntries(Module)} or {@link ModuleRootModel#orderEntries()} to process dependencies of a module
 * and use {@link #orderEntries(Project)} to process dependencies of all modules in a project.</p>
 *
 * <p>Note that all configuration methods modify {@link OrderEnumerator} instance instead of creating a new one.</p>
 *
 * @author nik
 * @since 10.0
 */
public abstract class OrderEnumerator {
  /**
   * Skip test dependencies
   *
   * @return this instance
   */
  public abstract OrderEnumerator productionOnly();

  /**
   * Skip runtime-only dependencies
   *
   * @return this instance
   */
  public abstract OrderEnumerator compileOnly();

  /**
   * Skip compile-only dependencies
   *
   * @return this instance
   */
  public abstract OrderEnumerator runtimeOnly();

  public abstract OrderEnumerator withoutSdk();

  public abstract OrderEnumerator withoutLibraries();

  public abstract OrderEnumerator withoutDepModules();

  /**
   * Skip root module's entries
   * @return this
   */
  public abstract OrderEnumerator withoutModuleSourceEntries();

  public OrderEnumerator librariesOnly() {
    return withoutSdk().withoutDepModules().withoutModuleSourceEntries();
  }

  public OrderEnumerator sdkOnly() {
    return withoutDepModules().withoutLibraries().withoutModuleSourceEntries();
  }

  public VirtualFile[] getAllLibrariesAndSdkClassesRoots() {
    return withoutModuleSourceEntries().withoutDepModules().recursively().exportedOnly().classes().usingCache().getRoots();
  }

  public VirtualFile[] getAllSourceRoots() {
    return recursively().exportedOnly().sources().usingCache().getRoots();
  }

  /**
   * Recursively process modules on which the module depends. This flag is ignored for modules imported from Maven because for such modules
   * transitive dependencies are propagated to the root module during importing.
   *
   * @return this instance
   */
  public abstract OrderEnumerator recursively();

  /**
   * Skip not exported dependencies. If this method is called after {@link #recursively()} direct non-exported dependencies won't be skipped
   *
   * @return this instance
   */
  public abstract OrderEnumerator exportedOnly();

  /**
   * Process only entries which satisfies the specified condition
   *
   * @param condition filtering condition
   * @return this instance
   */
  public abstract OrderEnumerator satisfying(Condition<OrderEntry> condition);

  /**
   * Use <code>provider.getRootModel()</code> to process module dependencies
   *
   * @param provider provider
   * @return this instance
   */
  public abstract OrderEnumerator using(@NotNull RootModelProvider provider);

  /**
   * Determine if, given the current enumerator settings and handlers for a module, should the
   * enumerator recurse to further modules based on the given ModuleOrderEntry?
   *
   * @param entry the ModuleOrderEntry in question (m1 -> m2)
   * @param handlers custom handlers registered to the module
   * @return true if the enumerator would have recursively processed the given ModuleOrderEntry.
   */
  public abstract boolean shouldRecurse(@NotNull ModuleOrderEntry entry, @NotNull List<OrderEnumerationHandler> handlers);

  /**
   * @return {@link OrderRootsEnumerator} instance for processing classes roots
   */
  public abstract OrderRootsEnumerator classes();

  /**
   * @return {@link OrderRootsEnumerator} instance for processing source roots
   */
  public abstract OrderRootsEnumerator sources();

  /**
   * @param rootType root type
   * @return {@link OrderRootsEnumerator} instance for processing roots of the specified type
   */
  public abstract OrderRootsEnumerator roots(@NotNull OrderRootType rootType);

  /**
   * @param rootTypeProvider custom root type provider
   * @return {@link OrderRootsEnumerator} instance for processing roots of the provided type
   */
  public abstract OrderRootsEnumerator roots(@NotNull NotNullFunction<OrderEntry, OrderRootType> rootTypeProvider);

  /**
   * @return classes roots for all entries processed by this enumerator
   */
  public VirtualFile[] getClassesRoots() {
    return classes().getRoots();
  }

  /**
   * @return source roots for all entries processed by this enumerator
   */
  public VirtualFile[] getSourceRoots() {
    return sources().getRoots();
  }

  /**
   * @return list containing classes roots for all entries processed by this enumerator
   */
  public PathsList getPathsList() {
    return classes().getPathsList();
  }

  /**
   * @return list containing source roots for all entries processed by this enumerator
   */
  public PathsList getSourcePathsList() {
    return sources().getPathsList();
  }

  /**
   * Runs <code>processor.process()</code> for each entry processed by this enumerator.
   *
   * @param processor processor
   */
  public abstract void forEach(@NotNull Processor<OrderEntry> processor);

  /**
   * Runs <code>processor.process()</code> for each library processed by this enumerator.
   *
   * @param processor processor
   */
  public abstract void forEachLibrary(@NotNull Processor<Library> processor);

  /**
   * Runs <code>processor.process()</code> for each module processed by this enumerator.
   *
   * @param processor processor
   */
  public abstract void forEachModule(@NotNull Processor<Module> processor);

  /**
   * Passes order entries to the specified visitor.
   *
   * @param policy       the visitor to accept.
   * @param initialValue the default value to be returned by the visit process.
   * @return the value returned by the visitor.
   * @see OrderEntry#accept(RootPolicy, Object)
   */
  public abstract <R> R process(@NotNull RootPolicy<R> policy, R initialValue);

  /**
   * Creates new enumerator instance to process dependencies of <code>module</code>
   *
   * @param module module
   * @return new enumerator instance
   */
  @NotNull
  public static OrderEnumerator orderEntries(@NotNull Module module) {
    return ModuleRootManager.getInstance(module).orderEntries();
  }

  /**
   * Creates new enumerator instance to process dependencies of all modules in <code>project</code>. Only first level dependencies of
   * modules are processed so {@link #recursively()} option is ignored and {@link #withoutDepModules()} option is forced
   *
   * @param project project
   * @return new enumerator instance
   */
  @NotNull
  public static OrderEnumerator orderEntries(@NotNull Project project) {
    return ProjectRootManager.getInstance(project).orderEntries();
  }
}