// Copyright 2015 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.packages.util;

import com.google.common.base.Joiner;
import com.google.common.collect.ImmutableList;
import com.google.devtools.build.lib.view.config.crosstool.CrosstoolConfig;
import com.google.protobuf.TextFormat;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.LinkedHashSet;
import java.util.Set;

/**
 * A helper class to create a crosstool package containing a CROSSTOOL file, and the various
 * rules needed for a mock - use this only for configured target tests, not for execution tests.
 */
final class Crosstool {
  private static final ImmutableList<String> CROSSTOOL_BINARIES =
      ImmutableList.of("compile", "dwp", "link", "objcopy");

  private final MockToolsConfig config;

  private final String crosstoolTop;
  private String version;
  private String crosstoolFileContents;
  private boolean addEmbeddedRuntimes;
  private String staticRuntimesLabel;
  private String dynamicRuntimesLabel;
  private ImmutableList<String> archs;
  private boolean addModuleMap;
  private boolean supportsHeaderParsing;

  Crosstool(MockToolsConfig config, String crosstoolTop) {
    this.config = config;
    this.crosstoolTop = crosstoolTop;
  }

  public Crosstool setAddModuleMap(boolean addModuleMap) {
    this.addModuleMap = addModuleMap;
    return this;
  }

  public Crosstool setCrosstoolFile(String version, String crosstoolFileContents) {
    this.version = version;
    this.crosstoolFileContents = crosstoolFileContents;
    return this;
  }

  public Crosstool setSupportedArchs(ImmutableList<String> archs) {
    this.archs = archs;
    return this;
  }

  public Crosstool setSupportsHeaderParsing(boolean supportsHeaderParsing) {
    this.supportsHeaderParsing = supportsHeaderParsing;
    return this;
  }

  public Crosstool setEmbeddedRuntimes(
      boolean addEmbeddedRuntimes, String staticRuntimesLabel, String dynamicRuntimesLabel) {
    this.addEmbeddedRuntimes = addEmbeddedRuntimes;
    this.staticRuntimesLabel = staticRuntimesLabel;
    this.dynamicRuntimesLabel = dynamicRuntimesLabel;
    return this;
  }

  public void write() throws IOException {
    String runtimes = "";
    for (String arch : archs) {
      runtimes +=
          Joiner.on('\n')
              .join(
                  "filegroup(name = 'dynamic-runtime-libs-" + arch + "',",
                  "          licenses = ['unencumbered'],",
                  "          srcs = ['libdynamic-runtime-lib-source.so'])",
                  "filegroup(name = 'static-runtime-libs-" + arch + "',",
                  "          licenses = ['unencumbered'],",
                  "          srcs = ['static-runtime-lib-source.a'])\n");
    }

    StringBuilder compilationTools = new StringBuilder();
    for (String compilationTool : CROSSTOOL_BINARIES) {
      Collection<String> archTargets = new ArrayList<>();
      for (String arch : archs) {
        archTargets.add(compilationTool + '-' + arch);
      }

      compilationTools.append(
          String.format(
              "filegroup(name = '%s', srcs = ['%s'])\n",
              compilationTool,
              Joiner.on("', '").join(archTargets)));
      for (String archTarget : archTargets) {
        compilationTools.append(
            String.format("filegroup(name = '%s', srcs = [':everything-multilib'])\n", archTarget));
      }
    }

    CrosstoolConfig.CrosstoolRelease.Builder configBuilder =
        CrosstoolConfig.CrosstoolRelease.newBuilder();
    TextFormat.merge(crosstoolFileContents, configBuilder);
    StringBuilder compilerMap = new StringBuilder();
    // Remove duplicates
    Set<String> keys = new LinkedHashSet<>();
    for (CrosstoolConfig.CToolchain toolchain : configBuilder.build().getToolchainList()) {
      String key = String.format("%s|%s", toolchain.getTargetCpu(), toolchain.getCompiler());
      if (!keys.contains(key)) {
        keys.add(key);
        compilerMap.append(
            String.format("'%s': ':cc-compiler-%s',\n", key, toolchain.getTargetCpu()));
      }
    }

    for (String arch : archs) {
      String compilerRule;
      String staticRuntimesString =
          staticRuntimesLabel == null ? "" : ", '" + staticRuntimesLabel + "'";
      String dynamicRuntimesString =
          dynamicRuntimesLabel == null ? "" : ", '" + dynamicRuntimesLabel + "'";

      compilerRule =
          Joiner.on("\n")
              .join(
                  "cc_toolchain(",
                  "    name = 'cc-compiler-" + arch + "',",
                  "    output_licenses = ['unencumbered'],",
                  addModuleMap ? "    module_map = 'crosstool.cppmap'," : "",
                  "    cpu = '" + arch + "',",
                  "    compiler_files = 'compile-" + arch + "',",
                  "    dwp_files = 'dwp-" + arch + "',",
                  "    linker_files = 'link-" + arch + "',",
                  "    strip_files = ':every-file',",
                  "    objcopy_files = 'objcopy-" + arch + "',",
                  "    all_files = ':every-file',",
                  "    licenses = ['unencumbered'],",
                  supportsHeaderParsing ? "    supports_header_parsing = 1," : "",
                  "    dynamic_runtime_libs = ['dynamic-runtime-libs-"
                      + arch
                      + "'"
                      + dynamicRuntimesString
                      + "],",
                  "    static_runtime_libs = ['static-runtime-libs-"
                      + arch
                      + "'"
                      + staticRuntimesString
                      + "])");

      compilationTools.append(compilerRule + "\n");
    }

    String build =
        Joiner.on("\n")
            .join(
                "package(default_visibility=['//visibility:public'])",
                "licenses(['restricted'])",
                "",
                "alias(name = 'toolchain', actual = 'everything')",
                "filegroup(name = 'everything-multilib',",
                "          srcs = glob(['" + version + "/**/*'],",
                "              exclude_directories = 1),",
                "          output_licenses = ['unencumbered'])",
                "",
                String.format(
                    "cc_toolchain_suite(name = 'everything', toolchains = {%s})", compilerMap),
                "",
                String.format(
                    "filegroup(name = 'every-file', srcs = ['%s'%s%s])",
                    Joiner.on("', '").join(CROSSTOOL_BINARIES),
                    addEmbeddedRuntimes ? ", ':dynamic-runtime-libs-k8'" : "",
                    addEmbeddedRuntimes ? ", ':static-runtime-libs-k8'" : ""),
                "",
                compilationTools.toString(),
                runtimes,
                "",
                // We add an empty :malloc target in case we need it.
                "cc_library(name = 'malloc')");

    config.create(crosstoolTop + "/" + version + "/x86/bin/gcc");
    config.create(crosstoolTop + "/" + version + "/x86/bin/ld");
    config.getPath(crosstoolTop + "/CROSSTOOL");
    config.overwrite(crosstoolTop + "/BUILD", build);
    config.overwrite(crosstoolTop + "/CROSSTOOL", crosstoolFileContents);
    config.create(crosstoolTop + "/crosstool.cppmap", "module crosstool {}");
  }
}
