

package com.facebook.buck.rules.macros;

import com.facebook.buck.core.cell.CellPathResolver;
import com.facebook.buck.core.exceptions.HumanReadableException;
import com.facebook.buck.core.macros.MacroException;
import com.facebook.buck.core.model.BuildTarget;
import com.facebook.buck.core.model.targetgraph.TargetGraph;
import com.facebook.buck.core.parser.buildtargetparser.BuildTargetPatternParser;
import com.facebook.buck.core.rules.ActionGraphBuilder;
import com.facebook.buck.query.NoopQueryEvaluator;
import com.facebook.buck.query.QueryBuildTarget;
import com.facebook.buck.query.QueryException;
import com.facebook.buck.query.QueryExpression;
import com.facebook.buck.query.QueryTarget;
import com.facebook.buck.rules.coercer.DefaultTypeCoercerFactory;
import com.facebook.buck.rules.coercer.TypeCoercerFactory;
import com.facebook.buck.rules.query.GraphEnhancementQueryEnvironment;
import com.facebook.buck.rules.query.Query;
import com.google.common.base.Preconditions;
import com.google.common.collect.ImmutableCollection;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableSet;
import java.util.Optional;
import java.util.Set;
import java.util.stream.Stream;

/** Abstract base class for the query_targets and query_outputs macros */
public abstract class QueryMacroExpander<T extends QueryMacro>
    extends AbstractMacroExpander<T, QueryMacroExpander.QueryResults> {

  private static final TypeCoercerFactory TYPE_COERCER_FACTORY = new DefaultTypeCoercerFactory();

  private Optional<TargetGraph> targetGraph;

  public QueryMacroExpander(Optional<TargetGraph> targetGraph) {
    this.targetGraph = targetGraph;
  }

  private Stream<BuildTarget> extractTargets(
      BuildTarget target,
      CellPathResolver cellNames,
      Optional<ActionGraphBuilder> graphBuilder,
      T input) {
    GraphEnhancementQueryEnvironment env =
        new GraphEnhancementQueryEnvironment(
            graphBuilder,
            targetGraph,
            TYPE_COERCER_FACTORY,
            cellNames,
            BuildTargetPatternParser.forBaseName(target.getBaseName()),
            ImmutableSet.of());
    try {
      QueryExpression parsedExp = QueryExpression.parse(input.getQuery().getQuery(), env);
      return parsedExp
          .getTargets(env)
          .stream()
          .map(
              queryTarget -> {
                Preconditions.checkState(queryTarget instanceof QueryBuildTarget);
                return ((QueryBuildTarget) queryTarget).getBuildTarget();
              });
    } catch (QueryException e) {
      throw new HumanReadableException(
          e,
          "Error executing query in macro for target %s: %s",
          target,
          e.getHumanReadableErrorMessage());
    }
  }

  Stream<QueryTarget> resolveQuery(
      BuildTarget target,
      CellPathResolver cellNames,
      ActionGraphBuilder graphBuilder,
      String queryExpression)
      throws MacroException {
    GraphEnhancementQueryEnvironment env =
        new GraphEnhancementQueryEnvironment(
            Optional.of(graphBuilder),
            targetGraph,
            TYPE_COERCER_FACTORY,
            cellNames,
            BuildTargetPatternParser.forBaseName(target.getBaseName()),
            ImmutableSet.of());
    try {
      QueryExpression parsedExp = QueryExpression.parse(queryExpression, env);
      Set<QueryTarget> queryTargets = new NoopQueryEvaluator().eval(parsedExp, env);
      return queryTargets.stream();
    } catch (QueryException e) {
      throw new MacroException("Error parsing/executing query from macro", e);
    }
  }

  @Override
  public Class<QueryResults> getPrecomputedWorkClass() {
    return QueryResults.class;
  }

  @Override
  public QueryResults precomputeWorkFrom(
      BuildTarget target, CellPathResolver cellNames, ActionGraphBuilder graphBuilder, T input)
      throws MacroException {
    return new QueryResults(
        resolveQuery(target, cellNames, graphBuilder, input.getQuery().getQuery()));
  }

  abstract T fromQuery(Query query);

  @Override
  protected T parse(BuildTarget target, CellPathResolver cellNames, ImmutableList<String> input)
      throws MacroException {
    if (input.size() != 1) {
      throw new MacroException("One quoted query expression is expected");
    }
    return fromQuery(Query.of(input.get(0), target.getBaseName()));
  }

  abstract boolean detectsTargetGraphOnlyDeps();

  @Override
  public void extractParseTimeDepsFrom(
      BuildTarget target,
      CellPathResolver cellNames,
      T input,
      ImmutableCollection.Builder<BuildTarget> buildDepsBuilder,
      ImmutableCollection.Builder<BuildTarget> targetGraphOnlyDepsBuilder) {
    extractTargets(target, cellNames, Optional.empty(), input)
        .forEach(
            (detectsTargetGraphOnlyDeps() ? targetGraphOnlyDepsBuilder : buildDepsBuilder)::add);
  }

  protected static final class QueryResults {
    ImmutableList<QueryTarget> results;

    public QueryResults(Stream<QueryTarget> results) {
      this.results = results.collect(ImmutableList.toImmutableList());
    }
  }
}
