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

package com.facebook.buck.rules.args;

import com.facebook.buck.model.BuildTarget;
import com.facebook.buck.model.MacroException;
import com.facebook.buck.model.MacroMatchResult;
import com.facebook.buck.rules.BuildRule;
import com.facebook.buck.rules.BuildRuleResolver;
import com.facebook.buck.rules.CellPathResolver;
import com.facebook.buck.rules.RuleKeyObjectSink;
import com.facebook.buck.rules.SourcePath;
import com.facebook.buck.rules.SourcePathResolver;
import com.facebook.buck.rules.SourcePathRuleFinder;
import com.facebook.buck.rules.macros.MacroHandler;
import com.facebook.buck.rules.macros.WorkerMacroExpander;
import com.facebook.buck.util.HumanReadableException;
import com.google.common.base.Function;
import com.google.common.base.Preconditions;
import com.google.common.collect.ImmutableCollection;
import com.google.common.collect.ImmutableList;
import java.util.Objects;

/** An {@link Arg} which contains macros that need to be expanded. */
public class MacroArg implements Arg {

  protected final MacroHandler expander;
  protected final BuildTarget target;
  protected final CellPathResolver cellNames;
  protected final BuildRuleResolver resolver;
  protected final String unexpanded;

  public MacroArg(
      MacroHandler expander,
      BuildTarget target,
      CellPathResolver cellNames,
      BuildRuleResolver resolver,
      String unexpanded) {
    this.expander = expander;
    this.target = target;
    this.cellNames = cellNames;
    this.resolver = resolver;
    this.unexpanded = unexpanded;
  }

  @Override
  public void appendToCommandLine(
      ImmutableCollection.Builder<String> builder, SourcePathResolver pathResolver) {
    try {
      builder.add(expander.expand(target, cellNames, resolver, unexpanded));
    } catch (MacroException e) {
      throw new HumanReadableException(e, "%s: %s", target, e.getMessage());
    }
  }

  @Override
  public ImmutableCollection<BuildRule> getDeps(SourcePathRuleFinder ruleFinder) {
    try {
      return expander.extractBuildTimeDeps(target, cellNames, resolver, unexpanded);
    } catch (MacroException e) {
      throw new HumanReadableException(e, "%s: %s", target, e.getMessage());
    }
  }

  @Override
  public ImmutableCollection<SourcePath> getInputs() {
    ImmutableCollection<BuildRule> rules;
    try {
      rules = expander.extractBuildTimeDeps(target, cellNames, resolver, unexpanded);
    } catch (MacroException e) {
      throw new HumanReadableException(e, "%s: %s", target, e.getMessage());
    }
    ImmutableList.Builder<SourcePath> paths = ImmutableList.builder();
    for (BuildRule rule : rules) {
      paths.add(Preconditions.checkNotNull(rule.getSourcePathToOutput()));
    }
    return paths.build();
  }

  @Override
  public void appendToRuleKey(RuleKeyObjectSink sink) {
    try {
      sink.setReflectively("arg", unexpanded)
          .setReflectively(
              "macros",
              expander.extractRuleKeyAppendables(target, cellNames, resolver, unexpanded));
    } catch (MacroException e) {
      throw new HumanReadableException(e, "%s: %s", target, e.getMessage());
    }
  }

  @Override
  public String toString() {
    return unexpanded;
  }

  @Override
  public boolean equals(Object o) {
    if (this == o) {
      return true;
    }
    if (!(o instanceof MacroArg)) {
      return false;
    }
    MacroArg macroArg = (MacroArg) o;
    return Objects.equals(target, macroArg.target)
        && Objects.equals(unexpanded, macroArg.unexpanded);
  }

  @Override
  public int hashCode() {
    return Objects.hash(target, unexpanded);
  }

  public static Function<String, Arg> toMacroArgFunction(
      final MacroHandler handler,
      final BuildTarget target,
      final CellPathResolver cellNames,
      final BuildRuleResolver resolver) {
    return new Function<String, Arg>() {
      @Override
      public MacroArg apply(String unexpanded) {
        try {
          if (containsWorkerMacro(handler, unexpanded)) {
            return new WorkerMacroArg(handler, target, cellNames, resolver, unexpanded);
          }
        } catch (MacroException e) {
          throw new HumanReadableException(e, "%s: %s", target, e.getMessage());
        }
        return new MacroArg(handler, target, cellNames, resolver, unexpanded);
      }
    };
  }

  public static boolean containsWorkerMacro(MacroHandler handler, String blob)
      throws MacroException {
    boolean result = false;
    for (MacroMatchResult matchResult : handler.getMacroMatchResults(blob)) {
      if (handler.getExpander(matchResult.getMacroType()) instanceof WorkerMacroExpander) {
        result = true;
      }
    }
    return result;
  }
}

@@ -1469,16 +1469,64 @@
 leList;%0A
+import java.util.HashMap;%0Aimport java.util.Map;%0A
 import j
@@ -1543,16 +1543,16 @@
 bjects;%0A
-
 %0A/** An 
@@ -1863,16 +1863,99 @@
 anded;%0A%0A
+  protected Map%3CMacroMatchResult, Object%3E precomputedWorkCache = new HashMap%3C%3E();%0A%0A
   public
@@ -2753,32 +2753,43 @@
 ctBuildTimeDeps(
+%0A          
 target, cellName
@@ -2803,32 +2803,54 @@
 lver, unexpanded
+, precomputedWorkCache
 );%0A    %7D catch (
@@ -3089,16 +3089,26 @@
  rules =
+%0A         
  expande
@@ -3130,16 +3130,31 @@
 imeDeps(
+%0A              
 target, 
@@ -3180,24 +3180,46 @@
 , unexpanded
+, precomputedWorkCache
 );%0A    %7D cat
@@ -3768,16 +3768,35 @@
 ndables(
+%0A                  
 target, 
@@ -3818,32 +3818,54 @@
 lver, unexpanded
+, precomputedWorkCache
 ));%0A    %7D catch 

Allow MacroArg to take advantage of query support for precomputed cache