/*
 * Copyright 2016-present Facebook, Inc.
 *
 * 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.facebook.buck.rules.macros;

import com.facebook.buck.model.BuildTarget;
import com.facebook.buck.model.MacroException;
import com.facebook.buck.parser.BuildTargetPatternParser;
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.BuildRuleResolver;
import com.facebook.buck.rules.CellPathResolver;
import com.facebook.buck.rules.TargetGraph;
import com.facebook.buck.rules.query.GraphEnhancementQueryEnvironment;
import com.facebook.buck.rules.query.Query;
import com.facebook.buck.util.HumanReadableException;
import com.google.common.base.CharMatcher;
import com.google.common.base.Preconditions;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableSet;
import com.google.common.util.concurrent.ListeningExecutorService;
import com.google.common.util.concurrent.MoreExecutors;

import java.util.HashSet;
import java.util.Optional;
import java.util.Set;
import java.util.stream.Collectors;
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> {

  private ListeningExecutorService executorService;
  private Optional<TargetGraph> targetGraph;

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

  private Stream<BuildTarget> extractTargets(
      BuildTarget target,
      CellPathResolver cellNames,
      Optional<BuildRuleResolver> resolver,
      T input) {
    String queryExpression = CharMatcher.anyOf("\"'").trimFrom(input.getQuery().getQuery());
    final GraphEnhancementQueryEnvironment env = new GraphEnhancementQueryEnvironment(
        resolver,
        targetGraph,
        cellNames,
        BuildTargetPatternParser.forBaseName(target.getBaseName()),
        ImmutableSet.of());
    try {
      QueryExpression parsedExp = QueryExpression.parse(queryExpression, env);
      HashSet<String> targetLiterals = new HashSet<>();
      parsedExp.collectTargetPatterns(targetLiterals);
      return targetLiterals.stream()
          .flatMap(pattern -> {
            try {
              return env.getTargetsMatchingPattern(pattern, executorService).stream();
            } catch (Exception e) {
              throw new HumanReadableException(
                  e,
                  "Error parsing target expression %s for target %",
                  pattern,
                  target);
            }
          })
          .map(queryTarget -> {
            Preconditions.checkState(queryTarget instanceof QueryBuildTarget);
            return ((QueryBuildTarget) queryTarget).getBuildTarget();
          });
    } catch (QueryException e) {
      throw new HumanReadableException("Error executing query in macro for target %s", target, e);
    }
  }

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

  abstract T fromQuery(Query query);

  @Override
  protected final 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)));
  }

  @Override
  public ImmutableList<BuildTarget> extractParseTimeDepsFrom(
      BuildTarget target,
      CellPathResolver cellNames,
      T input) {
    return ImmutableList.copyOf(extractTargets(target, cellNames, Optional.empty(), input)
        .collect(Collectors.toList()));
  }

}

@@ -3237,16 +3237,17 @@
 target %25
+s
 %22,%0A     

Fix botched exception message