// Copyright 2017 The Bazel Authors. 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.google.devtools.build.lib.rules.android;

import com.google.common.collect.ImmutableSet;
import com.google.devtools.build.lib.actions.Artifact;
import com.google.devtools.build.lib.analysis.RuleContext;
import com.google.devtools.build.lib.analysis.actions.CustomCommandLine;
import com.google.devtools.build.lib.analysis.actions.CustomCommandLine.VectorArg;
import com.google.devtools.build.lib.analysis.actions.SpawnAction;
import com.google.errorprone.annotations.CanIgnoreReturnValue;

/** Builder for creating a zip filter action. */
public class ZipFilterBuilder {
  /** Type of compression to apply to output archive. */
  public enum Compression {
    /** Output should be compressed. */
    COMPRESSED,
    /** Output should not be compressed. */
    UNCOMPRESSED,
    /** Compression should not change from input Zip. */
    DONT_CHANGE;
  }

  /** Modes of performing content hash checking during zip filtering. */
  public enum CheckHashMismatchMode {
    NONE,
    WARN,
    ERROR;
  }

  private final RuleContext ruleContext;
  private Artifact inputZip;
  private Artifact outputZip;
  private final ImmutableSet.Builder<Artifact> filterZipsBuilder;
  private final ImmutableSet.Builder<String> filterFileTypesBuilder;
  private final ImmutableSet.Builder<String> explicitFilterBuilder;
  private Compression outputMode = Compression.DONT_CHANGE;
  private CheckHashMismatchMode checkHashMismatch = CheckHashMismatchMode.WARN;

  /** Creates a builder using the configuration of the rule as the action configuration. */
  public ZipFilterBuilder(RuleContext ruleContext) {
    this.ruleContext = ruleContext;
    filterZipsBuilder = new ImmutableSet.Builder<>();
    filterFileTypesBuilder = new ImmutableSet.Builder<>();
    explicitFilterBuilder = new ImmutableSet.Builder<>();
  }

  /** Sets the Zip file to be filtered. */
  @CanIgnoreReturnValue
  public ZipFilterBuilder setInputZip(Artifact inputZip) {
    this.inputZip = inputZip;
    return this;
  }

  /** Sets the artifact to create with the action. */
  @CanIgnoreReturnValue
  public ZipFilterBuilder setOutputZip(Artifact outputZip) {
    this.outputZip = outputZip;
    return this;
  }

  /**
   * Adds to the Zip files to use as filters. Contents in these files will be omitted from the
   * output.
   */
  @CanIgnoreReturnValue
  public ZipFilterBuilder addFilterZips(Iterable<Artifact> filterZips) {
    this.filterZipsBuilder.addAll(filterZips);
    return this;
  }

  /**
   * Adds to the file types to use as filters. Only contents in the filter Zip files with these
   * extensions will be filtered out.
   */
  @CanIgnoreReturnValue
  public ZipFilterBuilder addFileTypeToFilter(String filterFileType) {
    this.filterFileTypesBuilder.add(filterFileType);
    return this;
  }

  /** Adds filterRegex to the set of filters to always check for and remove. */
  @CanIgnoreReturnValue
  public ZipFilterBuilder addExplicitFilter(String filterRegex) {
    this.explicitFilterBuilder.add(filterRegex);
    return this;
  }

  /** Enable checking of hash mismatches for files with the same name. */
  @CanIgnoreReturnValue
  public ZipFilterBuilder setCheckHashMismatchMode(CheckHashMismatchMode mode) {
    this.checkHashMismatch = mode;
    return this;
  }

  /** Builds the action as configured. */
  public void build() {
    ImmutableSet<Artifact> filterZips = filterZipsBuilder.build();
    ImmutableSet<String> filterFileTypes = filterFileTypesBuilder.build();
    ImmutableSet<String> explicitFilters = explicitFilterBuilder.build();

    CustomCommandLine.Builder args = CustomCommandLine.builder();
    args.addExecPath("--inputZip", inputZip);
    args.addExecPath("--outputZip", outputZip);
    if (!filterZips.isEmpty()) {
      args.addExecPaths("--filterZips", VectorArg.join(",").each(filterZips));
    }
    if (!filterFileTypes.isEmpty()) {
      args.addAll("--filterTypes", VectorArg.join(",").each(filterFileTypes));
    }
    if (!explicitFilters.isEmpty()) {
      args.addAll("--explicitFilters", VectorArg.join(",").each(explicitFilters));
    }
    switch (checkHashMismatch) {
      case WARN:
        args.add("--checkHashMismatch").add("WARN");
        break;
      case ERROR:
        args.add("--checkHashMismatch").add("ERROR");
        break;
      case NONE:
        args.add("--checkHashMismatch").add("IGNORE");
        break;
    }
    args.add("--outputMode");
    switch (outputMode) {
      case COMPRESSED:
        args.add("FORCE_DEFLATE");
        break;
      case UNCOMPRESSED:
        args.add("FORCE_STORED");
        break;
      case DONT_CHANGE:
      default:
        args.add("DONT_CARE");
        break;
    }

    ruleContext.registerAction(
        new SpawnAction.Builder()
            .addInput(inputZip)
            .addInputs(filterZips)
            .addOutput(outputZip)
            .setExecutable(ruleContext.getExecutablePrerequisite("$zip_filter"))
            .addCommandLine(args.build())
            .setProgressMessage("Filtering Zip %s", inputZip.prettyPrint())
            .setMnemonic("ZipFilter")
            .build(ruleContext));
  }
}
