// 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.Verify;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;
import com.google.devtools.build.lib.view.config.crosstool.CrosstoolConfig.CToolchain;

import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;

/**
 * An NdkStlImpl adds a specific Android NDK C++ STL implementation to a given CToolchain proto
 * builder.
 */
public abstract class StlImpl {

  private enum RuntimeType {
    DYNAMIC("so"), STATIC("a");

    private final String name, fileExtension;

    private RuntimeType(String fileExtension) {
      this.name = name().toLowerCase();
      this.fileExtension = fileExtension;
    }    
  }

  private final Map<String, String> fileGroupNameToFileGlobs = new LinkedHashMap<>();

  protected final String name;
  protected final NdkPaths ndkPaths;

  /**
   * @param name the name for this STL, which should be safe to use as a label
   * @param ndkPaths the NdkPaths
   */
  protected StlImpl(String name, NdkPaths ndkPaths) {
    this.name = name;
    this.ndkPaths = ndkPaths;
  }

  public void addStlImpl(
      List<CToolchain.Builder> baseToolchains, String gccVersion) {

    for (CToolchain.Builder baseToolchain : baseToolchains) {
      addStlImpl(baseToolchain, gccVersion);
    }    
  }

  /**
   * Adds an Android NDK C++ STL implementation to the given CToolchain builder.
   *
   * @param toolchain the toolchain to add the STL implementation to
   * @param gccVersion the gcc version for the STL impl. Applicable only to gnu-libstdc++
   */
  public abstract void addStlImpl(CToolchain.Builder toolchain, String gccVersion);

  protected void addBaseStlImpl(CToolchain.Builder toolchain, String gccVersion) {
 
    toolchain
      .setToolchainIdentifier(toolchain.getToolchainIdentifier() + "-" + name)
      .setSupportsEmbeddedRuntimes(true)
      .setDynamicRuntimesFilegroup(
          createRuntimeLibrariesFilegroup(
              name, gccVersion, toolchain.getTargetCpu(), RuntimeType.DYNAMIC))
      .setStaticRuntimesFilegroup(
          createRuntimeLibrariesFilegroup(
              name, gccVersion, toolchain.getTargetCpu(), RuntimeType.STATIC));
  }

  private String createRuntimeLibrariesFilegroup(
      String stl, String gccVersion, String targetCpu, RuntimeType type) {

    // gnu-libstlc++ has separate libraries for 4.8 and 4.9
    String fullStlName = stl;
    if (gccVersion != null) {
      fullStlName += "-" + gccVersion;
    }

    String filegroupNameTemplate = "%stl%-%targetCpu%-%type%-runtime-libraries";
    String filegroupName = filegroupNameTemplate
        .replace("%stl%", fullStlName)
        .replace("%targetCpu%", targetCpu)
        .replace("%type%", type.name);

    // At the same time that the filegroup name is created, record a corresponding file glob
    // pattern that AndroidNdkRepositoryFunction can look up later for creating the build file
    // rules.

    // These translations are unfortunate, but toolchain identifiers aren't allowed to have '+' in
    // their name.
    if (stl.equals("gnu-libstdcpp")) {
      stl = "gnu-libstdc++";
    }
    if (stl.equals("libcpp")) {
      stl = "llvm-libc++";
    }

    String glob = NdkPaths.createStlRuntimeLibsGlob(stl, gccVersion, targetCpu, type.fileExtension);
    String previousValue = fileGroupNameToFileGlobs.put(filegroupName, glob);

    // Some STL filegroups will end up being duplicates, but a filegroup should never be registered
    // with a different glob, otherwise one toolchain would get the wrong glob.
    Verify.verify(previousValue == null || previousValue.equals(glob),
        "STL filegroup glob being replaced with a different glob:\nname: %s\n%s\n%s",
        filegroupName, glob, previousValue);
    
    return filegroupName;
  }

  protected static Iterable<String> createIncludeFlags(Iterable<String> includePaths) {
    ImmutableList.Builder<String> includeFlags = ImmutableList.builder();
    for (String includePath : includePaths) {
      includeFlags.add("-isystem");
      includeFlags.add(includePath);
    }
    return includeFlags.build();
  }

  /**
   * @return a map of the names of the generated STL runtime library filegroup names to their file
   * glob patterns
   */
  public Map<String, String> getFilegroupNamesAndFilegroupFileGlobPatterns() {
    return ImmutableMap.copyOf(fileGroupNameToFileGlobs);
  }

  /**
   * @return the name of this STL impl, which is safe to use as a label
   */
  public String getName() {
    return name;
  }
}

