// Copyright 2016 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.annotations.VisibleForTesting;
import com.google.common.base.Function;
import com.google.common.base.Functions;
import com.google.common.base.Joiner;
import com.google.common.base.Preconditions;
import com.google.common.collect.FluentIterable;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.Iterables;
import com.google.common.collect.Iterators;
import com.google.devtools.build.lib.actions.Artifact;
import com.google.devtools.build.lib.analysis.actions.CustomCommandLine;
import com.google.devtools.build.lib.collect.nestedset.NestedSet;
import com.google.devtools.build.lib.collect.nestedset.NestedSetBuilder;
import com.google.devtools.build.lib.collect.nestedset.Order;
import com.google.devtools.build.lib.rules.android.AndroidResourcesProvider.ResourceContainer;
import com.google.devtools.build.lib.rules.android.AndroidResourcesProvider.ResourceType;
import javax.annotation.Nullable;

/**
 * Factory for functions to convert a {@link ResourceContainer} to a commandline argument, or a
 * collection of artifacts. Uses a certain convention for commandline arguments (e.g., separators,
 * and ordering of container elements).
 */
@VisibleForTesting
public class ResourceContainerConverter {

  static Builder builder() {
    return new Builder();
  }

  interface ToArg extends Function<ResourceContainer, String> {

    String listSeparator();
  }

  interface ToArtifacts extends Function<ResourceContainer, NestedSet<Artifact>> {

  }

  static class Builder {

    private boolean includeResourceRoots;
    private boolean includeLabel;
    private boolean includeManifest;
    private boolean includeRTxt;
    private boolean includeSymbolsBin;
    private SeparatorType separatorType;
    private Joiner argJoiner;
    private Function<String, String> escaper = Functions.identity();

    enum SeparatorType {
      COLON_COMMA,
      SEMICOLON_AMPERSAND
    }

    Builder() {
    }

    Builder includeResourceRoots() {
      includeResourceRoots = true;
      return this;
    }

    Builder includeLabel() {
      includeLabel = true;
      return this;
    }

    Builder includeManifest() {
      includeManifest = true;
      return this;
    }

    Builder includeRTxt() {
      includeRTxt = true;
      return this;
    }

    Builder includeSymbolsBin() {
      includeSymbolsBin = true;
      return this;
    }

    Builder withSeparator(SeparatorType type) {
      separatorType = type;
      return this;
    }

    ToArg toArgConverter() {
      switch (separatorType) {
        case COLON_COMMA:
          argJoiner = Joiner.on(":");
          // We currently use ":" to separate components of an argument and "," to separate
          // arguments in a list of arguments. Those characters require escaping if used in a label
          // (part of the set of allowed characters in a label).
          if (includeLabel) {
            escaper = new Function<String, String>() {
              @Override
              public String apply(String input) {
                return input.replace(":", "\\:").replace(",", "\\,");
              }
            };
          }
          break;
        case SEMICOLON_AMPERSAND:
          argJoiner = Joiner.on(";");
          break;
        default:
          Preconditions.checkState(false, "Unknown separator type " + separatorType);
          break;
      }

      return new ToArg() {
        @Override
        public String apply(ResourceContainer container) {
          ImmutableList.Builder<String> cmdPieces = ImmutableList.builder();
          if (includeResourceRoots) {
            cmdPieces.add(convertRoots(container, ResourceType.RESOURCES));
            cmdPieces.add(convertRoots(container, ResourceType.ASSETS));
          }
          if (includeLabel) {
            cmdPieces.add(escaper.apply(container.getLabel().toString()));
          }
          if (includeManifest) {
            cmdPieces.add(container.getManifest().getExecPathString());
          }
          if (includeRTxt) {
            cmdPieces.add(
                container.getRTxt() == null ? "" : container.getRTxt().getExecPathString());
          }
          if (includeSymbolsBin) {
            cmdPieces.add(
                container.getSymbolsTxt() == null
                    ? ""
                    : container.getSymbolsTxt().getExecPathString());
          }
          return argJoiner.join(cmdPieces.build());
        }

        @Override
        public String listSeparator() {
          switch (separatorType) {
            case COLON_COMMA:
              return ",";
            case SEMICOLON_AMPERSAND:
              return "&";
            default:
              Preconditions.checkState(false, "Unknown separator type " + separatorType);
              return null;
          }
        }
      };
    }

    ToArtifacts toArtifactConverter() {
      return new ToArtifacts() {
        @Override
        public NestedSet<Artifact> apply(ResourceContainer container) {
          NestedSetBuilder<Artifact> artifacts = NestedSetBuilder.naiveLinkOrder();
          if (includeResourceRoots) {
            artifacts.addAll(container.getArtifacts());
          }
          if (includeManifest) {
            addIfNotNull(container.getManifest(), artifacts);
          }
          if (includeRTxt) {
            addIfNotNull(container.getRTxt(), artifacts);
          }
          if (includeSymbolsBin) {
            addIfNotNull(container.getSymbolsTxt(), artifacts);
          }
          return artifacts.build();
        }
      };
    }
  }

  private static void addIfNotNull(
      @Nullable Artifact artifact, NestedSetBuilder<Artifact> artifacts) {
    if (artifact != null) {
      artifacts.add(artifact);
    }
  }

  @VisibleForTesting
  public static String convertRoots(ResourceContainer container, ResourceType resourceType) {
    return Joiner.on("#")
        .join(
            Iterators.transform(
                container.getRoots(resourceType).iterator(), Functions.toStringFunction()));
  }

  /**
   * Convert ResourceDependencies to commandline args and artifacts, assuming the commandline
   * arguments should be split into direct deps and transitive deps.
   */
  static void convertDependencies(
      ResourceDependencies dependencies,
      CustomCommandLine.Builder cmdBuilder,
      NestedSetBuilder<Artifact> inputs,
      ToArg toArg,
      ToArtifacts toArtifacts) {

    if (dependencies != null) {
      // TODO(bazel-team): Find an appropriately lazy method to deduplicate the dependencies between
      // the direct and transitive data.
      // Add transitive data inside an unmodifiableIterable to ensure it won't be expanded until
      // iteration.
      if (!dependencies.getTransitiveResources().isEmpty()) {
        cmdBuilder.addJoinStrings(
            "--data",
            toArg.listSeparator(),
            Iterables.unmodifiableIterable(
                Iterables.transform(dependencies.getTransitiveResources(), toArg)));
      }
      // Add direct data inside an unmodifiableIterable to ensure it won't be expanded until
      // iteration.
      if (!dependencies.getDirectResources().isEmpty()) {
        cmdBuilder.addJoinStrings(
            "--directData",
            toArg.listSeparator(),
            Iterables.unmodifiableIterable(
                Iterables.transform(dependencies.getDirectResources(), toArg)));
      }
      // This flattens the nested set. Since each ResourceContainer needs to be transformed into
      // Artifacts, and the NestedSetBuilder.wrap doesn't support lazy Iterator evaluation
      // and SpawnActionBuilder.addInputs evaluates Iterables, it becomes necessary to make the
      // best effort and let it get flattened.
      inputs.addTransitive(
          NestedSetBuilder.wrap(
              Order.NAIVE_LINK_ORDER,
              FluentIterable.from(dependencies.getResources()).transformAndConcat(toArtifacts)));
    }
  }
}
