// Copyright 2014 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.cpp;

import com.google.common.base.Predicate;
import com.google.common.collect.ImmutableList;
import com.google.devtools.build.lib.actions.Artifact;
import com.google.devtools.build.lib.util.FileType;
import com.google.devtools.build.lib.util.FileTypeSet;
import com.google.devtools.build.lib.vfs.OsPathPolicy;
import java.util.regex.Pattern;

/**
 * C++-related file type definitions.
 */
public final class CppFileTypes {
  private static final OsPathPolicy OS = OsPathPolicy.getFilePathOs();

  // .cu and .cl are CUDA and OpenCL source extensions, respectively. They are expected to only be
  // supported with clang. Bazel is not officially supporting these targets, and the extensions are
  // listed only as long as they work with the existing C++ actions.
  // FileType is extended to use case-sensitive comparison also on Windows
  public static final FileType CPP_SOURCE =
      new FileType() {
        final ImmutableList<String> extensions =
            ImmutableList.of(".cc", ".cpp", ".cxx", ".c++", ".C", ".cu", ".cl");

        @Override
        public boolean apply(String path) {
          for (String ext : extensions) {
            if (path.endsWith(ext)) {
              return true;
            }
          }
          return false;
        }

        @Override
        public ImmutableList<String> getExtensions() {
          return extensions;
        }
      };

  // FileType is extended to use case-sensitive comparison also on Windows
  public static final FileType C_SOURCE =
      new FileType() {
        final String ext = ".c";

        @Override
        public boolean apply(String path) {
          return path.endsWith(ext);
        }

        @Override
        public ImmutableList<String> getExtensions() {
          return ImmutableList.of(ext);
        }
      };

  public static final FileType OBJC_SOURCE = FileType.of(".m");
  public static final FileType OBJCPP_SOURCE = FileType.of(".mm");
  public static final FileType CLIF_INPUT_PROTO = FileType.of(".ipb");
  public static final FileType CLIF_OUTPUT_PROTO = FileType.of(".opb");
  public static final FileType BC_SOURCE = FileType.of(".bc");

  public static final FileTypeSet ALL_C_CLASS_SOURCE =
      FileTypeSet.of(
          CppFileTypes.CPP_SOURCE,
          CppFileTypes.C_SOURCE,
          CppFileTypes.OBJCPP_SOURCE,
          CppFileTypes.OBJC_SOURCE,
          CppFileTypes.CLIF_INPUT_PROTO);

  // Filetypes that generate LLVM bitcode when -flto is specified.
  public static final FileTypeSet LTO_SOURCE =
      FileTypeSet.of(CppFileTypes.CPP_SOURCE, CppFileTypes.C_SOURCE);

  public static final FileType CPP_HEADER =
      FileType.of(
          ".h", ".hh", ".hpp", ".ipp", ".hxx", ".h++", ".inc", ".inl", ".tlh", ".tli", ".H",
          ".tcc");
  public static final FileType PCH = FileType.of(".pch");
  public static final FileTypeSet OBJC_HEADER = FileTypeSet.of(CPP_HEADER, PCH);

  public static final FileType CPP_TEXTUAL_INCLUDE = FileType.of(".inc");

  public static final FileType PIC_PREPROCESSED_C = FileType.of(".pic.i");
  public static final FileType PREPROCESSED_C =
      new FileType() {
        final String ext = ".i";

        @Override
        public boolean apply(String path) {
          return OS.endsWith(path, ext) && !PIC_PREPROCESSED_C.matches(path);
        }

        @Override
        public ImmutableList<String> getExtensions() {
          return ImmutableList.of(ext);
        }
      };
  public static final FileType PIC_PREPROCESSED_CPP = FileType.of(".pic.ii");
  public static final FileType PREPROCESSED_CPP =
      new FileType() {
        final String ext = ".ii";

        @Override
        public boolean apply(String path) {
          return OS.endsWith(path, ext) && !PIC_PREPROCESSED_CPP.matches(path);
        }

        @Override
        public ImmutableList<String> getExtensions() {
          return ImmutableList.of(ext);
        }
      };

  // FileType is extended to use case-sensitive comparison also on Windows
  public static final FileType ASSEMBLER_WITH_C_PREPROCESSOR =
      new FileType() {
        final String ext = ".S";

        @Override
        public boolean apply(String path) {
          return path.endsWith(ext);
        }

        @Override
        public ImmutableList<String> getExtensions() {
          return ImmutableList.of(ext);
        }
      };

  // FileType is extended to use case-sensitive comparison also on Windows
  public static final FileType PIC_ASSEMBLER =
      new FileType() {
        final String ext = ".pic.s";

        @Override
        public boolean apply(String path) {
          return OS.endsWith(path, ext) && path.endsWith(".s");
        }

        @Override
        public ImmutableList<String> getExtensions() {
          return ImmutableList.of(ext);
        }
      };

  // FileType is extended to use case-sensitive comparison also on Windows
  public static final FileType ASSEMBLER =
      new FileType() {
        final String ext = ".s";

        @Override
        public boolean apply(String path) {
          return (path.endsWith(ext) && !PIC_ASSEMBLER.matches(path)) || OS.endsWith(path, ".asm");
        }

        @Override
        public ImmutableList<String> getExtensions() {
          return ImmutableList.of(ext, ".asm");
        }
      };

  public static final FileType PIC_ARCHIVE = FileType.of(".pic.a");
  public static final FileType ARCHIVE =
      new FileType() {
        final ImmutableList<String> extensions = ImmutableList.of(".a", ".lib");

        @Override
        public boolean apply(String path) {
          if (PIC_ARCHIVE.matches(path)
              || ALWAYS_LINK_LIBRARY.matches(path)
              || OS.endsWith(path, ".if.lib")) {
            return false;
          }
          for (String ext : extensions) {
            if (OS.endsWith(path, ext)) {
              return true;
            }
          }
          return false;
        }

        @Override
        public ImmutableList<String> getExtensions() {
          return extensions;
        }
      };

  public static final FileType ALWAYS_LINK_PIC_LIBRARY = FileType.of(".pic.lo");
  public static final FileType ALWAYS_LINK_LIBRARY =
      new FileType() {
        final String ext = ".lo";

        @Override
        public boolean apply(String path) {
          return (OS.endsWith(path, ext) && !ALWAYS_LINK_PIC_LIBRARY.matches(path))
              || OS.endsWith(path, ".lo.lib");
        }

        @Override
        public ImmutableList<String> getExtensions() {
          return ImmutableList.of(ext, ".lo.lib");
        }
      };

  public static final FileType PIC_OBJECT_FILE = FileType.of(".pic.o");
  public static final FileType OBJECT_FILE =
      new FileType() {
        final String ext = ".o";

        @Override
        public boolean apply(String path) {
          return (OS.endsWith(path, ext) && !PIC_OBJECT_FILE.matches(path))
              || OS.endsWith(path, ".obj");
        }

        @Override
        public ImmutableList<String> getExtensions() {
          return ImmutableList.of(ext, ".obj");
        }
      };
  // Static library artifact created by rustc, can be used as a regular archive.
  public static final FileType RUST_RLIB = FileType.of(".rlib");

  // Minimized bitcode file emitted by the ThinLTO compile step and used just for LTO indexing.
  public static final FileType LTO_INDEXING_OBJECT_FILE = FileType.of(".indexing.o");

  // Imports file emitted by the ThinLTO indexing step and used for LTO backend action.
  public static final FileType LTO_IMPORTS_FILE = FileType.of(".imports");

  // Indexing analysis result file emitted by the ThinLTO indexing step and used for LTO backend
  // action.
  public static final FileType LTO_INDEXING_ANALYSIS_FILE = FileType.of(".thinlto.bc");

  // TODO(bazel-team): File types should not be read from this hard-coded list but should come from
  // the toolchain instead. See https://github.com/bazelbuild/bazel/issues/17117
  public static final FileType SHARED_LIBRARY =
      FileType.of(".so", ".dylib", ".dll", ".pyd", ".wasm", ".tgt", ".vpi");
  // Unix shared libraries can be passed to linker, but not .dll on Windows
  public static final FileType UNIX_SHARED_LIBRARY = FileType.of(".so", ".dylib");
  public static final FileType INTERFACE_SHARED_LIBRARY =
      FileType.of(".ifso", ".tbd", ".lib", ".dll.a");
  public static final FileType LINKER_SCRIPT = FileType.of(".ld", ".lds", ".ldscript");

  // Windows DEF file: https://msdn.microsoft.com/en-us/library/28d6s79h.aspx
  public static final FileType WINDOWS_DEF_FILE = FileType.of(".def");

  // Matches shared libraries with version names in the extension, i.e.
  // libmylib.so.2 or libmylib.so.2.10 or libmylib.so.1a_b35.
  private static final Pattern VERSIONED_SHARED_LIBRARY_PATTERN =
      Pattern.compile("^.+\\.((so)|(dylib))(\\.\\d\\w*)+$");
  public static final FileType VERSIONED_SHARED_LIBRARY =
      new FileType() {
        @Override
        public boolean apply(String path) {
          // Because regex matching can be slow, we first do a quick check for ".so." and ".dylib."
          // substring before risking the full-on regex match. This should eliminate the performance
          // hit on practically every non-qualifying file type.
          if (!path.contains(".so.") && !path.contains(".dylib.")) {
            return false;
          }
          return VERSIONED_SHARED_LIBRARY_PATTERN.matcher(path).matches();
        }
      };

  public static final FileType COVERAGE_NOTES = FileType.of(".gcno");
  public static final FileType GCC_AUTO_PROFILE = FileType.of(".afdo");
  public static final FileType XBINARY_PROFILE = FileType.of(".xfdo");
  public static final FileType LLVM_PROFILE = FileType.of(".profdata");
  public static final FileType LLVM_PROFILE_RAW = FileType.of(".profraw");
  public static final FileType LLVM_PROFILE_ZIP = FileType.of(".zip");

  public static final FileType CPP_MODULE_MAP = FileType.of(".cppmap");
  public static final FileType CPP_MODULE = FileType.of(".pcm", ".gcm", ".ifc");
  public static final FileType OBJC_MODULE_MAP = FileType.of("module.modulemap");

  /** Predicate that matches all artifacts that can be used in an objc Clang module map. */
  public static final Predicate<Artifact> MODULE_MAP_HEADER =
      artifact -> {
        if (artifact.isTreeArtifact()) {
          // Tree artifact is basically a directory, which does not have any information about
          // the contained files and their extensions. Here we assume the passed in tree artifact
          // contains proper header files with .h extension.
          return true;
        } else {
          // The current clang (clang-600.0.57) on Darwin doesn't support 'textual', so we can't
          // have '.inc' files in the module map (since they're implictly textual).
          // TODO(bazel-team): Use HEADERS file type once clang-700 is the base clang we support.
          return OS.endsWith(artifact.getFilename(), ".h");
        }
      };

  public static final boolean headerDiscoveryRequired(Artifact source) {
    String fileName = source.getFilename();
    return !ASSEMBLER.matches(fileName)
        && !PIC_ASSEMBLER.matches(fileName)
        && !CPP_MODULE.matches(fileName);
  }

  private CppFileTypes() {}
}
