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

import com.facebook.buck.rules.args.Arg;
import com.facebook.buck.rules.args.StringArg;
import com.google.common.collect.ImmutableCollection;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.ImmutableSortedSet;

import java.util.Optional;

/**
 * A {@link Tool} based on a list of arguments formed by {@link SourcePath}s.
 *
 * Example:
 * <pre>
 * {@code
 *   Tool compiler = new CommandTool.Builder()
 *      .addArg(compilerPath)
 *      .addArg("-I%s", defaultIncludeDir)
 *      .build();
 * }
 * </pre>
 */
public class CommandTool implements Tool {

  private final Optional<Tool> baseTool;
  private final ImmutableList<Arg> args;
  private final ImmutableMap<String, Arg> environment;
  private final ImmutableSortedSet<SourcePath> extraInputs;
  private final ImmutableSortedSet<BuildRule> extraDeps;

  private CommandTool(
      Optional<Tool> baseTool,
      ImmutableList<Arg> args,
      ImmutableMap<String, Arg> environment,
      ImmutableSortedSet<SourcePath> extraInputs,
      ImmutableSortedSet<BuildRule> extraDeps) {
    this.baseTool = baseTool;
    this.args = args;
    this.environment = environment;
    this.extraInputs = extraInputs;
    this.extraDeps = extraDeps;
  }

  @Override
  public ImmutableCollection<SourcePath> getInputs() {
    ImmutableSortedSet.Builder<SourcePath> inputs = ImmutableSortedSet.naturalOrder();
    if (baseTool.isPresent()) {
      inputs.addAll(baseTool.get().getInputs());
    }
    for (Arg arg : args) {
      inputs.addAll(arg.getInputs());
    }
    inputs.addAll(extraInputs);
    return inputs.build();
  }

  @Override
  public ImmutableCollection<BuildRule> getDeps(SourcePathRuleFinder ruleFinder) {
    ImmutableSortedSet.Builder<BuildRule> deps = ImmutableSortedSet.naturalOrder();
    if (baseTool.isPresent()) {
      deps.addAll(baseTool.get().getDeps(ruleFinder));
    }
    deps.addAll(ruleFinder.filterBuildRuleInputs(getInputs()));
    deps.addAll(extraDeps);
    return deps.build();
  }

  @Override
  public ImmutableList<String> getCommandPrefix(SourcePathResolver resolver) {
    ImmutableList.Builder<String> command = ImmutableList.builder();
    if (baseTool.isPresent()) {
      command.addAll(baseTool.get().getCommandPrefix(resolver));
    }
    for (Arg arg : args) {
      arg.appendToCommandLine(command);
    }
    return command.build();
  }

  @Override
  public ImmutableMap<String, String> getEnvironment() {
    ImmutableMap.Builder<String, String> env = ImmutableMap.builder();
    if (baseTool.isPresent()) {
      env.putAll(baseTool.get().getEnvironment());
    }
    env.putAll(Arg.stringify(environment));
    return env.build();
  }

  @Override
  public void appendToRuleKey(RuleKeyObjectSink sink) {
    sink
        .setReflectively("baseTool", baseTool)
        .setReflectively("args", args)
        .setReflectively("extraInputs", extraInputs);
  }

  // Builder for a `CommandTool`.
  public static class Builder {

    private final Optional<Tool> baseTool;
    private final ImmutableList.Builder<Arg> args = ImmutableList.builder();
    private final ImmutableMap.Builder<String, Arg> environment = ImmutableMap.builder();
    private final ImmutableSortedSet.Builder<SourcePath> extraInputs =
        ImmutableSortedSet.naturalOrder();
    private final ImmutableSortedSet.Builder<BuildRule> extraDeps =
        ImmutableSortedSet.naturalOrder();

    public Builder(Optional<Tool> baseTool) {
      this.baseTool = baseTool;
    }

    public Builder(Tool baseTool) {
      this(Optional.of(baseTool));
    }

    public Builder() {
      this(Optional.empty());
    }

    /**
     * Adds an argument.
     */
    public Builder addArg(Arg arg) {
      args.add(arg);
      return this;
    }

    public Builder addArg(String arg) {
      return addArg(new StringArg(arg));
    }

    /**
     * Adds an environment variable key=arg.
     */
    public Builder addEnv(String key, Arg arg) {
      environment.put(key, arg);
      return this;
    }

    public Builder addEnv(String key, String val) {
      return addEnv(key, new StringArg(val));
    }

    /**
     * Adds additional non-argument inputs to the tool.
     */
    public Builder addInputs(Iterable<? extends SourcePath> inputs) {
      extraInputs.addAll(inputs);
      return this;
    }

    public Builder addInput(SourcePath... inputs) {
      return addInputs(ImmutableList.copyOf(inputs));
    }

    /**
     * Adds additional non-argument deps to the tool.
     */
    public Builder addDeps(Iterable<? extends BuildRule> deps) {
      extraDeps.addAll(deps);
      return this;
    }

    public Builder addDep(BuildRule... deps) {
      return addDeps(ImmutableList.copyOf(deps));
    }

    public CommandTool build() {
      return new CommandTool(
          baseTool,
          args.build(),
          environment.build(),
          extraInputs.build(),
          extraDeps.build());
    }

  }

}
