// 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.bazel.rules.android.ndkcrosstools;

import com.google.common.base.Preconditions;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.Lists;
import com.google.devtools.build.lib.rules.cpp.CppConfiguration;
import com.google.devtools.build.lib.rules.cpp.CppConfiguration.Tool;
import com.google.devtools.build.lib.view.config.crosstool.CrosstoolConfig.ToolPath;
import java.util.Arrays;
import java.util.List;

/**
 * Class for creating paths that are specific to the structure of the Android NDK, but which are
 * common to all crosstool toolchains.
 */
public class NdkPaths {

  /**
   * Removes the NDK repository prefix from the given path. Eg:
   * "external/%repositoryName%/ndk/a/b/c" -> "ndk/a/b/c"
   */
  public static String stripRepositoryPrefix(String path) {
    return path.split("/", 3)[2];
  }

  private final String repositoryName;
  private final String hostPlatform;
  private final ApiLevel apiLevel;
  private final Integer majorRevision;
  private final String externalExecPathBase;

  public NdkPaths(
      String repositoryName,
      String hostPlatform,
      ApiLevel apiLevel,
      Integer majorRevision,
      boolean siblingRepositoryLayout) {
    this.repositoryName = repositoryName;
    this.hostPlatform = hostPlatform;
    this.apiLevel = apiLevel;
    this.majorRevision = majorRevision;
    this.externalExecPathBase = siblingRepositoryLayout ? ".." : "external";
  }

  public ImmutableList<ToolPath> createToolpaths(String toolchainName, String targetPlatform,
      CppConfiguration.Tool... excludedTools) {

    ImmutableList.Builder<ToolPath> toolPaths = ImmutableList.builder();

    for (Tool tool : CppConfiguration.Tool.values()) {

      // Some toolchains don't have particular tools.
      if (!Arrays.asList(excludedTools).contains(tool)) {

        String toolPath = createToolPath(toolchainName, targetPlatform + "-" + tool.getNamePart());

        toolPaths.add(ToolPath.newBuilder()
            .setName(tool.getNamePart())
            .setPath(toolPath)
            .build());
      }
    }

    return toolPaths.build();
  }

  public ImmutableList<ToolPath> createClangToolpaths(String toolchainName, String targetPlatform,
      String llvmVersion, CppConfiguration.Tool... excludedTools) {

    // Add GCC to the list of excluded tools. It will be replaced by clang below.
    excludedTools = ImmutableList.<CppConfiguration.Tool>builder()
        .add(excludedTools)
        .add(CppConfiguration.Tool.GCC)
        .build()
        .toArray(new CppConfiguration.Tool[excludedTools.length + 1]);

    // Create the regular tool paths, then add clang.
    return ImmutableList.<ToolPath>builder()
        .addAll(createToolpaths(toolchainName, targetPlatform, excludedTools))

        .add(ToolPath.newBuilder()
            .setName("gcc")
            .setPath(createToolPath(llvmVersion == null ? "llvm" : "llvm-" + llvmVersion, "clang"))
            .build())
        .build();
  }

  private String createToolPath(String toolchainName, String toolName) {

    String toolpathTemplate = "ndk/toolchains/%toolchainName%/prebuilt/%hostPlatform%"
        + "/bin/%toolName%";

    return toolpathTemplate
        .replace("%repositoryName%", repositoryName)
        .replace("%toolchainName%", toolchainName)
        .replace("%hostPlatform%", hostPlatform)
        .replace("%toolName%", toolName);
  }

  public static String getToolchainDirectoryFromToolPath(String toolPath) {
    return toolPath.split("/")[2];
  }

  public String createGccToolchainPath(String toolchainName) {

    String gccToolchainPathTemplate =
        "%externalExecPathBase%/%repositoryName%/ndk/toolchains/%toolchainName%/prebuilt"
            + "/%hostPlatform%";

    return gccToolchainPathTemplate
        .replace("%externalExecPathBase%", externalExecPathBase)
        .replace("%repositoryName%", repositoryName)
        .replace("%toolchainName%", toolchainName)
        .replace("%hostPlatform%", hostPlatform);
  }

  /**
   * Gets the clang NDK builtin includes directories that exist in the NDK. These directories are
   * always searched for header files by clang and should be added to the CROSSTOOL in the
   * cxx_builtin_include_directories list.
   *
   * <p>You can see the list of directories and the order that they are searched in by running
   * {@code clang -E -x c++ - -v < /dev/null}.
   */
  public String createClangToolchainBuiltinIncludeDirectory(String clangVersion) {
    String clangBuiltinIncludeDirectoryPathTemplate =
        "%externalExecPathBase%/%repositoryName%/ndk/toolchains/llvm/prebuilt/%hostPlatform%/lib64/"
            + "clang/%clangVersion%/include";
    return clangBuiltinIncludeDirectoryPathTemplate
        .replace("%externalExecPathBase%", externalExecPathBase)
        .replace("%repositoryName%", repositoryName)
        .replace("%hostPlatform%", hostPlatform)
        .replace("%clangVersion%", clangVersion);
  }

  /**
   * Gets the gcc NDK builtin includes directories that exist in the NDK. These directories are
   * always searched for header files by clang and should be added to the CROSSTOOL in the
   * cxx_builtin_include_directories list.
   *
   * <p>You can see the list of directories and the order that they are searched in by running
   * {@code gcc -E -x c++ - -v < /dev/null}.
   */
  public List<String> createGccToolchainBuiltinIncludeDirectories(
      final String toolchainName, final String targetPlatform, final String gccVersion) {
    final String toolchainIncludePathTemplate =
        "%externalExecPathBase%/%repositoryName%/ndk/toolchains/%toolchainName%/prebuilt/"
            + "%hostPlatform%/lib/gcc/%targetPlatform%/%gccVersion%/%includeFolderName%";
    return Lists.transform(
        ImmutableList.of("include", "include-fixed"),
        includeFolderName ->
            toolchainIncludePathTemplate
                .replace("%externalExecPathBase%", externalExecPathBase)
                .replace("%repositoryName%", repositoryName)
                .replace("%toolchainName%", toolchainName)
                .replace("%hostPlatform%", hostPlatform)
                .replace("%targetPlatform%", targetPlatform)
                .replace("%gccVersion%", gccVersion)
                .replace("%includeFolderName%", includeFolderName));
  }

  /**
   * NDK 14 and below. Each API level has its own headers. See
   * https://android.googlesource.com/platform/ndk/+/ndk-r15-release/docs/UnifiedHeaders.md#supporting-unified-headers-in-your-build-system
   *
   * @param targetCpu the target CPU architecture
   * @return the path to the compile time sysroot
   */
  public String createBuiltinSysroot(String targetCpu) {

    String correctedApiLevel = apiLevel.getCpuCorrectedApiLevel(targetCpu);

    String androidPlatformIncludePathTemplate =
        "%externalExecPathBase%/%repositoryName%/ndk/platforms/android-%apiLevel%/arch-%arch%";

    return androidPlatformIncludePathTemplate
        .replace("%externalExecPathBase%", externalExecPathBase)
        .replace("%repositoryName%", repositoryName)
        .replace("%apiLevel%", correctedApiLevel)
        .replace("%arch%", targetCpu);
  }

  /**
   * NDK 15 and above. The headers have been unified into ndk/sysroot.
   *
   * @return the sysroot location for NDK 15 and above.
   */
  public String createBuiltinSysroot() {
    // This location does not exist prior to NDK 15
    Preconditions.checkState(majorRevision >= 15);

    return "%externalExecPathBase%/%repositoryName%/ndk/sysroot"
        .replace("%externalExecPathBase%", externalExecPathBase)
        .replace("%repositoryName%", repositoryName);
  }

  public String getCorrectedApiLevel(String targetCpu) {
    return apiLevel.getCpuCorrectedApiLevel(targetCpu);
  }

  ImmutableList<String> createGnuLibstdcIncludePaths(String gccVersion, String targetCpu) {

    String cpuNoThumb = targetCpu.replaceAll("-thumb$", "");

    String prefix =
        "%externalExecPathBase%/%repositoryName%/ndk/sources/cxx-stl/gnu-libstdc++/%gccVersion%/";
    List<String> includePathTemplates = Arrays.asList(
        prefix + "include",
        prefix + "libs/%targetCpu%/include",
        prefix + "include/backward");

    ImmutableList.Builder<String> includePaths = ImmutableList.builder();
    for (String template : includePathTemplates) {
      includePaths.add(
          template
              .replace("%externalExecPathBase%", externalExecPathBase)
              .replace("%repositoryName%", repositoryName)
              .replace("%gccVersion%", gccVersion)
              .replace("%targetCpu%", cpuNoThumb));
    }
    return includePaths.build();
  }

  ImmutableList<String> createStlportIncludePaths() {

    String prefix =
        "%externalExecPathBase%/%repositoryName%/ndk/sources/cxx-stl/"
            .replace("%externalExecPathBase%", externalExecPathBase)
            .replace("%repositoryName%", repositoryName);

    return ImmutableList.of(prefix + "stlport/stlport", prefix + "gabi++/include");
  }

  ImmutableList<String> createLibcxxIncludePaths() {

    String prefix =
        "%externalExecPathBase%/%repositoryName%/ndk/sources/"
            .replace("%externalExecPathBase%", externalExecPathBase)
            .replace("%repositoryName%", repositoryName);

    ImmutableList.Builder<String> includePaths = ImmutableList.builder();
    
    if (majorRevision <= 12) {
      includePaths.add(prefix + "cxx-stl/llvm-libc++/libcxx/include");
      includePaths.add(prefix + "cxx-stl/llvm-libc++abi/libcxxabi/include");
    } else {
      // libcxx/include was moved one level up from r13 onwards.
      // See https://github.com/bazelbuild/bazel/issues/3641
      includePaths.add(prefix + "cxx-stl/llvm-libc++/include");
      includePaths.add(prefix + "cxx-stl/llvm-libc++abi/include");
    }

    includePaths.add(prefix + "android/support/include");

    return includePaths.build();
  }

  /**
   * @param stl The STL name as it appears in the NDK path
   * @param gccVersion The GCC version "4.8" or "4.9", applicable only to gnu-libstdc++, or null
   * @param targetCpu Target CPU
   * @param fileExtension "a" or "so"
   * @return A glob pattern for the STL runtime libs in the NDK.
   */
  static String createStlRuntimeLibsGlob(
      String stl, String gccVersion, String targetCpu, String fileExtension) {

    if (gccVersion != null) {
      stl += "/" + gccVersion;
    }

    targetCpu = targetCpu.replaceAll("-thumb$", "/thumb");

    String template =
        "ndk/sources/cxx-stl/%stl%/libs/%targetCpu%/*.%fileExtension%";
    return template
        .replace("%stl%", stl)
        .replace("%targetCpu%", targetCpu)
        .replace("%fileExtension%", fileExtension);
  }

  /**
   * @param targetCpu Target CPU
   * @return the directory of the target CPU's runtime .a files for linking
   */
  public String createLibcppLinkerPath(String targetCpu) {
    return "%externalExecPathBase%/%repositoryName%/ndk/sources/cxx-stl/llvm-libc++/libs/%targetCpu%"
        .replace("%externalExecPathBase%", externalExecPathBase)
        .replace("%repositoryName%", repositoryName)
        .replace("%targetCpu%", targetCpu);
  }
}

