// 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 java.util.List;
import java.util.regex.Pattern;

/**
 * C++-related file type definitions.
 */
public final class CppFileTypes {
  public static final FileType CPP_SOURCE = FileType.of(".cc", ".cpp", ".cxx", ".c++", ".C");
  public static final FileType C_SOURCE = FileType.of(".c");
  public static final FileType OBJC_SOURCE = FileType.of(".m");
  public static final FileType OBJCPP_SOURCE = FileType.of(".mm");

  // 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", ".inc");
  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 filename) {
        return filename.endsWith(ext) && !PIC_PREPROCESSED_C.matches(filename);
      }
      @Override
      public List<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 filename) {
        return filename.endsWith(ext) && !PIC_PREPROCESSED_CPP.matches(filename);
      }
      @Override
      public List<String> getExtensions() {
        return ImmutableList.of(ext);
      }
    };

  public static final FileType ASSEMBLER_WITH_C_PREPROCESSOR = FileType.of(".S");
  public static final FileType PIC_ASSEMBLER = FileType.of(".pic.s");
  public static final FileType ASSEMBLER = new FileType() {
      final String ext = ".s";
      @Override
      public boolean apply(String filename) {
        return (filename.endsWith(ext) && !PIC_ASSEMBLER.matches(filename))
               || filename.endsWith(".asm");
      }
      @Override
      public List<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 List<String> extensions = ImmutableList.of(".a", ".lib");

        @Override
        public boolean apply(String filename) {
          for (String ext : extensions) {
            if (filename.endsWith(ext) && !PIC_ARCHIVE.matches(filename)) {
              return true;
            }
          }
          return false;
        }

        @Override
        public List<String> getExtensions() {
          return ImmutableList.copyOf(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 filename) {
        return filename.endsWith(ext) && !ALWAYS_LINK_PIC_LIBRARY.matches(filename);
      }
      @Override
      public List<String> getExtensions() {
        return ImmutableList.of(ext);
      }
    };

  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 filename) {
        return filename.endsWith(ext) && !PIC_OBJECT_FILE.matches(filename);
      }
      @Override
      public List<String> getExtensions() {
        return ImmutableList.of(ext);
      }
    };

  // 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");

  public static final FileType SHARED_LIBRARY = FileType.of(".so", ".dylib", ".dll");
  public static final FileType INTERFACE_SHARED_LIBRARY = FileType.of(".ifso");
  public static final FileType LINKER_SCRIPT = FileType.of(".ld", ".lds", ".ldscript");
  // Matches shared libraries with version names in the extension, i.e.
  // libmylib.so.2 or libmylib.so.2.10.
  private static final Pattern VERSIONED_SHARED_LIBRARY_PATTERN =
     Pattern.compile("^.+\\.so(\\.\\d+)+$");
  public static final FileType VERSIONED_SHARED_LIBRARY = new FileType() {
      @Override
      public boolean apply(String filename) {
        // Because regex matching can be slow, we first do a quick digit check on the final
        // character before risking the full-on regex match. This should eliminate the performance
        // hit on practically every non-qualifying file type.
        if (Character.isDigit(filename.charAt(filename.length() - 1))) {
          return VERSIONED_SHARED_LIBRARY_PATTERN.matcher(filename).matches();
        } else {
          return false;
        }
      }
    };

  public static final FileType COVERAGE_NOTES = FileType.of(".gcno");
  public static final FileType COVERAGE_DATA = FileType.of(".gcda");
  public static final FileType COVERAGE_DATA_IMPORTS = FileType.of(".gcda.imports");
  public static final FileType GCC_AUTO_PROFILE = FileType.of(".afdo");
  public static final FileType LLVM_PROFILE = FileType.of(".profdata");
  public static final FileType LLVM_PROFILE_RAW = FileType.of(".profraw");

  public static final FileType CPP_MODULE_MAP = FileType.of(".cppmap");
  public static final FileType CPP_MODULE = FileType.of(".pcm");

  // Output of the dwp tool
  public static final FileType DEBUG_INFO_PACKAGE = FileType.of(".dwp");

  public static final FileType CLIF_INPUT_PROTO = FileType.of(".ipb");
  public static final FileType CLIF_OUTPUT_PROTO = FileType.of(".opb");

  /** 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 artifact.getFilename().endsWith(".h");
        }
      };

  public static final boolean headerDiscoveryRequired(Artifact source) {
    // Sources from TreeArtifacts and TreeFileArtifacts will not generate dotd file.
    if (source.isTreeArtifact() || source.hasParent()) {
      return false;
    }

    String fileName = source.getFilename();
    return !ASSEMBLER.matches(fileName)
        && !PIC_ASSEMBLER.matches(fileName)
        && !CPP_MODULE.matches(fileName);
  }

}
