/*
 * Copyright 2016-present Facebook, Inc.
 *
 * 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.facebook.buck.cxx;

import com.facebook.buck.io.ProjectFilesystem;
import com.facebook.infer.annotation.Assertions;
import com.google.common.collect.FluentIterable;
import com.google.common.collect.ImmutableBiMap;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;

import java.io.IOException;
import java.nio.file.Path;
import java.util.Map;
import java.util.Optional;


/**
 * This sanitizer works by depending on the compiler's -fdebug-prefix-map flag to properly ensure
 * that the output only contains references to the mapped-to paths (i.e. the fake paths).
 */
public class PrefixMapDebugPathSanitizer extends DebugPathSanitizer {
  protected final ImmutableBiMap<Path, Path> allPaths;
  private final ProjectFilesystem projectFilesystem;
  private boolean isGcc;
  private Path compilationDir;

  // Save so we can make a copy later
  private final ImmutableBiMap<Path, Path> other;
  private final CxxToolProvider.Type cxxType;

  public PrefixMapDebugPathSanitizer(
      int pathSize,
      char separator,
      Path fakeCompilationDirectory,
      ImmutableBiMap<Path, Path> other,
      Path realCompilationDirectory,
      CxxToolProvider.Type cxxType,
      ProjectFilesystem projectFilesystem) {
    super(
        separator, pathSize,
        fakeCompilationDirectory);
    this.projectFilesystem = projectFilesystem;
    this.isGcc = cxxType == CxxToolProvider.Type.GCC;
    this.compilationDir = realCompilationDirectory;
    // Save for later
    this.other = other;
    this.cxxType = cxxType;

    ImmutableBiMap.Builder<Path, Path> pathsBuilder = ImmutableBiMap.builder();
    // As these replacements are processed one at a time, if one is a prefix (or actually is just
    // contained in) another, it must be processed after that other one. To ensure that we can
    // process them in the correct order, they are inserted into allPaths in order of length
    // (longest first). Then, if they are processed in the order in allPaths, prefixes will be
    // handled correctly.
    pathsBuilder.putAll(
        FluentIterable.from(other.entrySet())
            .toSortedList(
                (left, right) ->
                    right.getKey().toString().length() - left.getKey().toString().length()
                )
    );
    // We assume that nothing in other is a prefix of realCompilationDirectory (though the reverse
    // is fine).
    pathsBuilder.put(realCompilationDirectory, fakeCompilationDirectory);
    for (Path p : other.keySet()) {
      Assertions.assertCondition(!realCompilationDirectory.toString().contains(p.toString()));
    }

    this.allPaths = pathsBuilder.build();
  }

  @Override
  ImmutableMap<String, String> getCompilationEnvironment(
      Path workingDir, boolean shouldSanitize) {
    if (!workingDir.equals(compilationDir)) {
      throw new AssertionError(String.format(
          "Expected working dir (%s) to be same as compilation dir (%s)",
          workingDir,
          compilationDir));
    }
    return ImmutableMap.of(
        "PWD", workingDir.toString());
  }

  @Override
  void restoreCompilationDirectory(Path path, Path workingDir) throws IOException {
    Assertions.assertCondition(workingDir.equals(compilationDir));
    // There should be nothing to sanitize in the compilation directory because the compilation
    // flags took care of it.
  }

  @Override
  ImmutableList<String> getCompilationFlags() {
    ImmutableList.Builder<String> flags = ImmutableList.builder();
    // Two -fdebug-prefix-map flags will be applied in the reverse order, so reverse allPaths.
    Iterable<Map.Entry<Path, Path>> iter = ImmutableList.copyOf(allPaths.entrySet()).reverse();
    for (Map.Entry<Path, Path> mappings : iter) {
      flags.add(getDebugPrefixMapFlag(mappings.getKey(), mappings.getValue()));
    }
    if (isGcc) {
      // If we recorded switches in the debug info, the -fdebug-prefix-map values would contain the
      // unsanitized paths.
      flags.add("-gno-record-gcc-switches");
    }
    return flags.build();
  }


  private String getDebugPrefixMapFlag(Path realPath, Path fakePath) {
    return String.format(
        "-fdebug-prefix-map=%s=%s",
        realPath,
        fakePath);
  }

  @Override
  protected ImmutableBiMap<Path, Path> getAllPaths(Optional<Path> workingDir) {
    if (workingDir.isPresent()) {
      Assertions.assertCondition(workingDir.get().equals(compilationDir));
    }
    // We need to always sanitize the real workingDir because we add it directly into the flags.
    return allPaths;
  }

  @Override
  public DebugPathSanitizer withProjectFilesystem(ProjectFilesystem projectFilesystem) {
    if (this.projectFilesystem.equals(projectFilesystem)) {
      return this;
    }
    // TODO(mzlee): Do not create a new sanitizer every time
    return new PrefixMapDebugPathSanitizer(
        this.pathSize,
        this.separator,
        this.compilationDirectory,
        this.other,
        projectFilesystem.getRootPath(),
        this.cxxType,
        projectFilesystem);
  }
}

@@ -678,16 +678,53 @@
 system;%0A
+import com.facebook.buck.log.Logger;%0A
 import c
@@ -1330,16 +1330,99 @@
 tizer %7B%0A
+  private static final Logger LOG = Logger.get(PrefixMapDebugPathSanitizer.class);%0A
   protec
@@ -5477,24 +5477,161 @@
 this;%0A    %7D%0A
+    LOG.debug(%0A        %22Creating a new PrefixMapDebugPathSanitizer with projectFilesystem %25s%22,%0A        projectFilesystem.getRootPath());%0A
     // TODO(

Add a LOG.debug when changing the project filesystem