/*
 * Copyright 2015-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.
 */

// Copyright 2014 Google Inc. All rights reserved.
//
// 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.query;

import com.facebook.buck.core.model.QueryTarget;
import com.facebook.buck.query.QueryEnvironment.Argument;
import com.facebook.buck.query.QueryEnvironment.ArgumentType;
import com.facebook.buck.query.QueryEnvironment.QueryFunction;
import com.google.common.base.Predicate;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.Iterables;
import java.util.LinkedHashSet;
import java.util.Set;

/**
 * A 'rdeps(u, x, [, depth])' expression, which finds the reverse dependencies of the given argument
 * set 'x' within the transitive closure of the set 'u'. The optional parameter 'depth' specifies
 * the depth of the search. If the argument is absent, the search is unbounded.
 *
 * <pre>expr ::= RDEPS '(' expr ',' expr ')'</pre>
 *
 * <pre>       | RDEPS '(' expr ',' expr ',' WORD ')'</pre>
 */
public class RdepsFunction<T extends QueryTarget> implements QueryFunction<T, T> {

  private static final ImmutableList<ArgumentType> ARGUMENT_TYPES =
      ImmutableList.of(ArgumentType.EXPRESSION, ArgumentType.EXPRESSION, ArgumentType.INTEGER);

  public RdepsFunction() {}

  @Override
  public String getName() {
    return "rdeps";
  }

  @Override
  public int getMandatoryArguments() {
    return 2;
  }

  @Override
  public ImmutableList<ArgumentType> getArgumentTypes() {
    return ARGUMENT_TYPES;
  }

  /**
   * Evaluates to the reverse dependencies of the argument 'x' in the transitive closure of the set
   * 'u'. Breadth first search from the set 'x' until there are no more unvisited nodes in the
   * reverse transitive closure or the maximum depth (if supplied) is reached.
   */
  @Override
  public ImmutableSet<T> eval(
      QueryEvaluator<T> evaluator, QueryEnvironment<T> env, ImmutableList<Argument<T>> args)
      throws QueryException {
    Set<T> universeSet = evaluator.eval(args.get(0).getExpression(), env);
    env.buildTransitiveClosure(universeSet, Integer.MAX_VALUE);
    Predicate<T> inUniversePredicate = env.getTransitiveClosure(universeSet)::contains;

    // LinkedHashSet preserves the order of insertion when iterating over the values.
    // The order by which we traverse the result is meaningful because the dependencies are
    // traversed level-by-level.
    Set<T> visited = new LinkedHashSet<>();
    Set<T> current = evaluator.eval(args.get(1).getExpression(), env);
    Predicate<T> notVisited = target -> !visited.contains(target);

    int depthBound = args.size() > 2 ? args.get(2).getInteger() : Integer.MAX_VALUE;
    // Iterating depthBound+1 times because the first one processes the given argument set.
    for (int i = 0; i <= depthBound; i++) {
      // Restrict the search to nodes in the transitive closure of the universe set.
      Iterable<T> currentInUniverse = Iterables.filter(current, inUniversePredicate);

      // Filter nodes visited before.
      Set<T> next = env.getReverseDeps(Iterables.filter(currentInUniverse, notVisited));
      Iterables.addAll(visited, currentInUniverse);
      if (next.isEmpty()) {
        break;
      }
      current = next;
    }
    return ImmutableSet.copyOf(visited);
  }
}

@@ -3228,39 +3228,39 @@
     
-Predicate%3CT%3E inUniversePredicat
+Set%3CT%3E transitiveClosureUnivers
 e = 
@@ -3300,18 +3300,8 @@
 Set)
-::contains
 ;%0A%0A 
@@ -3629,645 +3629,714 @@
 v);%0A
+%0A
     
-Predicate%3CT%3E notVisited = target -%3E !visited.contains(target);%0A%0A    int depthBound = args.size() %3E 2 ? args.get(2).getInteger() : Integer.MAX_VALUE;%0A    // Iterating depthBound+1 times because the first one processes the given argument set.%0A    for (int i = 0; i %3C= depthBound; i++) %7B%0A      // Restrict the search to nodes in the transitive closure of the universe set.%0A      Iterable%3CT%3E currentInUniverse = Iterables.filter(current, inUniversePredicate);%0A%0A      // Filter nodes visited before.%0A      Set%3CT%3E next = env.getReverseDeps(Iterables.filter(currentInUniverse, notVisited));%0A      Iterables.addAll(visited, currentInUniverse
+// This predicate function does not just do filtering but also populates visited collection.%0A    // This is a bit ugly but enables to evaluate the collection exactly once%0A    // The actual evaluation and population of %60visited%60 happens in %60getReverseDeps%60 as it iterates%0A    // through targets%0A    Predicate%3CT%3E filter =%0A        target -%3E (transitiveClosureUniverse.contains(target) && visited.add(target));%0A%0A    int depthBound = args.size() %3E 2 ? args.get(2).getInteger() : Integer.MAX_VALUE;%0A    // Iterating depthBound+1 times because the first one processes the given argument set.%0A    for (int i = 0; i %3C= depthBound; i++) %7B%0A      Set%3CT%3E next = env.getReverseDeps(Iterables.filter(current, filter)
 );%0A 

Optimize RdepsFunction to iterate less on same data