/*
 * Copyright (C) 2014 Google 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.google.common.annotations.checkers;

import static com.google.common.truth.Truth.assertThat;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;

import com.google.common.collect.ArrayListMultimap;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMultiset;
import com.google.common.collect.ListMultimap;
import com.google.common.io.Files;
import com.google.common.primitives.Longs;
import com.google.errorprone.BaseErrorProneJavaCompiler;
import com.google.errorprone.BugPattern;
import com.google.errorprone.bugpatterns.BugChecker;
import com.google.errorprone.scanner.ScannerSupplier;
import java.io.File;
import java.io.PrintWriter;
import java.util.Arrays;
import java.util.List;
import java.util.Locale;
import javax.tools.Diagnostic;
import javax.tools.DiagnosticCollector;
import javax.tools.JavaCompiler;
import javax.tools.JavaCompiler.CompilationTask;
import javax.tools.JavaFileObject;

/**
 * Compiles Java sources with the {@link BetaChecker} and returns a list of diagnostics produced
 * by the compiler.
 *
 * @author Colin Decker
 */
final class TestCompiler {

  private final Class<? extends BugChecker> checker;

  TestCompiler(Class<? extends BugChecker> checker) {
    this.checker = checker;
  }

  // TODO(cgdecker): Would like to use compile-testing to avoid the need for this class

  /**
   * Compiles the given {@code sources} and returns a list of diagnostics produced by the compiler.
   */
  public List<Diagnostic<? extends JavaFileObject>> compile(JavaFileObject... sources) {
    return compile(Arrays.asList(sources));
  }

  /**
   * Compiles the given {@code sources} and returns a list of diagnostics produced by the compiler.
   */
  public List<Diagnostic<? extends JavaFileObject>> compile(
      Iterable<? extends JavaFileObject> sources) {
    ScannerSupplier scannerSupplier = ScannerSupplier.fromBugCheckerClasses(checker);
    DiagnosticCollector<JavaFileObject> collector = new DiagnosticCollector<>();
    JavaCompiler compiler = new BaseErrorProneJavaCompiler(scannerSupplier);
    File tmpDir = Files.createTempDir();
    CompilationTask task =
        compiler.getTask(
            new PrintWriter(System.err, true),
            null /*filemanager*/,
            collector,
            ImmutableList.of("-d", tmpDir.getAbsolutePath()),
            null /*classes*/,
            sources);
    try {
      task.call();
      return collector.getDiagnostics();
    } finally {
      File[] files = tmpDir.listFiles();
      if (files != null) {
        for (File file : files) {
          file.delete();
        }
      }
      tmpDir.delete();
    }
  }

  /**
   * Asserts that the given diagnostics contain errors with a message containing "[CheckerName]"
   * on the given lines of the given file. If there should be multiple errors on a line, the line
   * number must appear multiple times. There may not be any errors in any other file.
   */
  public void assertErrorsOnLines(String file,
      List<Diagnostic<? extends JavaFileObject>> diagnostics, long... lines) {
    ListMultimap<String, Long> actualErrors = ArrayListMultimap.create();
    for (Diagnostic<? extends JavaFileObject> diagnostic : diagnostics) {
      String message = diagnostic.getMessage(Locale.US);

      // The source may be null, e.g. for diagnostics about command-line flags
      assertNotNull(message, diagnostic.getSource());
      String sourceName = diagnostic.getSource().getName();

      assertEquals(
          "unexpected error in source file " + sourceName + ": " + message,
          file, sourceName);

      actualErrors.put(diagnostic.getSource().getName(), diagnostic.getLineNumber());

      // any errors from the compiler that are not related to this checker should fail
      assertThat(message).contains("[" + checker.getAnnotation(BugPattern.class).name() + "]");
    }

    assertEquals(
        ImmutableMultiset.copyOf(Longs.asList(lines)),
        ImmutableMultiset.copyOf(actualErrors.get(file)));
  }
}