// 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.ImmutableList;
import com.google.common.collect.Iterators;
import com.google.devtools.build.lib.analysis.actions.CommandLineItem;
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.rules.android.ResourceContainer.ResourceType;

/**
 * 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 CommandLineItem.MapFn<ResourceContainer> {

    String listSeparator();
  }

  static class Builder {

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

    enum SeparatorType {
      COLON_COMMA,
      SEMICOLON_AMPERSAND
    }

    Builder() {}

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

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

    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 includeCompiledSymbols() {
      includeCompiledSymbols = 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 = (String input) -> 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 expandToCommandLine(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 (includeAapt2RTxt) {
            cmdPieces.add(
                container.getAapt2RTxt() == null
                    ? ""
                    : container.getAapt2RTxt().getExecPathString());
          }
          if (includeStaticLibrary) {
            cmdPieces.add(
                container.getStaticLibrary() == null
                    ? ""
                    : container.getStaticLibrary().getExecPathString());
          }
          if (includeCompiledSymbols) {
            cmdPieces.add(
                container.getCompiledSymbols() == null
                    ? ""
                    : container.getCompiledSymbols().getExecPathString());
          }
          if (includeSymbolsBin) {
            cmdPieces.add(
                container.getSymbols() == null ? "" : container.getSymbols().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;
          }
        }
      };
    }
  }

  @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 addToCommandLine(
      ResourceDependencies dependencies, CustomCommandLine.Builder cmdBuilder, ToArg toArg) {
    cmdBuilder.addAll(
        "--data",
        VectorArg.join(toArg.listSeparator())
            .each(dependencies.getTransitiveResourceContainers())
            .mapped(toArg));
    cmdBuilder.addAll(
        "--directData",
        VectorArg.join(toArg.listSeparator())
            .each(dependencies.getDirectResourceContainers())
            .mapped(toArg));
  }
}
