/*
 * Copyright 2015 The Error Prone Authors.
 *
 * 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.errorprone.bugpatterns;

import static com.google.common.collect.ImmutableList.toImmutableList;
import static java.util.stream.Collectors.joining;

import com.google.common.base.Predicate;
import com.google.common.base.Predicates;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.Streams;
import com.google.errorprone.BugCheckerRefactoringTestHelper;
import com.google.errorprone.CompilationTestHelper;
import java.util.regex.Pattern;
import java.util.stream.Stream;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.JUnit4;

/** {@link WildcardImport}Test */
@RunWith(JUnit4.class)
public class WildcardImportTest {
  private BugCheckerRefactoringTestHelper testHelper;

  @Before
  public void setUp() {
    testHelper = BugCheckerRefactoringTestHelper.newInstance(WildcardImport.class, getClass());
  }

  @Test
  public void chainOffStatic() {
    testHelper
        .addInputLines(
            "a/One.java",
            "package a;",
            "public class One {",
            "  public static Two THE_INSTANCE = null;",
            "}")
        .expectUnchanged()
        .addInputLines(
            "a/Two.java",
            "package a;",
            "public class Two {",
            "  public static String MESSAGE = \"Hello\";",
            "}")
        .expectUnchanged()
        .addInputLines(
            "in/test/Test.java",
            "package test;",
            "import static a.One.*;",
            "public class Test {",
            "  String m = THE_INSTANCE.MESSAGE;",
            "}")
        .addOutputLines(
            "out/test/Test.java",
            "package test;",
            "import static a.One.THE_INSTANCE;",
            "public class Test {",
            "  String m = THE_INSTANCE.MESSAGE;",
            "}")
        .doTest();
  }

  @Test
  public void classLiteral() {
    testHelper
        .addInputLines("a/A.java", "package a;", "public class A {", "}")
        .expectUnchanged()
        .addInputLines(
            "in/test/Test.java",
            "package test;",
            "import a.*;",
            "public class Test {",
            "  void m() {",
            "     System.err.println(A.class);",
            "  }",
            "}")
        .addOutputLines(
            "out/test/Test.java",
            "package test;",
            "import a.A;",
            "public class Test {",
            "  void m() {",
            "     System.err.println(A.class);",
            "  }",
            "}")
        .doTest();
  }

  @Test
  public void staticMethod() {
    testHelper
        .addInputLines(
            "a/A.java", //
            "package a;",
            "public class A {",
            "  public static void f() {}",
            "}")
        .expectUnchanged()
        .addInputLines(
            "in/test/Test.java",
            "package test;",
            "import static a.A.*;",
            "public class Test {",
            "  void m() {",
            "    f();",
            "  }",
            "}")
        .addOutputLines(
            "out/test/Test.java",
            "package test;",
            "import static a.A.f;",
            "public class Test {",
            "  void m() {",
            "    f();",
            "  }",
            "}")
        .doTest();
  }

  @Test
  public void enumTest() {
    testHelper
        .addInputLines(
            "in/test/Test.java",
            "package test;",
            "import static java.nio.charset.StandardCharsets.*;",
            "public class Test {",
            "  void m() {",
            "    System.err.println(UTF_8);",
            "  }",
            "}")
        .addOutputLines(
            "out/test/Test.java",
            "package test;",
            "import static java.nio.charset.StandardCharsets.UTF_8;",
            "public class Test {",
            "  void m() {",
            "    System.err.println(UTF_8);",
            "  }",
            "}")
        .doTest();
  }

  @Test
  public void positive() {
    testHelper
        .addInputLines(
            "in/test/Test.java",
            "package test;",
            "import java.util.*;",
            "public class Test {",
            "    java.util.Map.Entry<String, String> e;",
            "    C c;",
            "    static class C {}",
            "}")
        .addOutputLines(
            "out/test/Test.java",
            "package test;",
            "public class Test {",
            "    java.util.Map.Entry<String, String> e;",
            "    C c;",
            "    static class C {}",
            "}")
        .doTest();
  }

  @Test
  public void doublePrefix() {
    testHelper
        .addInputLines(
            "foo/Foo.java", //
            "package foo;",
            "public class Foo {}")
        .expectUnchanged()
        .addInputLines(
            "foo/bar/Bar.java", //
            "package foo.bar;",
            "public class Bar {}")
        .expectUnchanged()
        .addInputLines(
            "in/test/Test.java",
            "package test;",
            "import foo.*;",
            "import foo.bar.*;",
            "public class Test {",
            "    void f(Bar c) {}",
            "}")
        .addOutputLines(
            "out/test/Test.java",
            "package test;",
            "import foo.bar.Bar;",
            "public class Test {",
            "    void f(Bar c) {}",
            "}")
        .doTest();
  }

  @Test
  public void positiveClassSelect() {
    testHelper
        .addInputLines(
            "in/test/Test.java",
            "package test;",
            "import java.util.*;",
            "public class Test {",
            "    Map.Entry<String, String> e;",
            "    C c;",
            "    static class C {}",
            "}")
        .addOutputLines(
            "out/test/Test.java",
            "package test;",
            "import java.util.Map;",
            "public class Test {",
            "    Map.Entry<String, String> e;",
            "    C c;",
            "    static class C {}",
            "}")
        .doTest();
  }

  @Test
  public void positiveInnerClass() {
    testHelper
        .addInputLines(
            "in/test/Test.java",
            "package test;",
            "import java.util.Map.*;",
            "public class Test {",
            "    Entry<String, String> e;",
            "    C c;",
            "    static class C {}",
            "}")
        .addOutputLines(
            "out/test/Test.java",
            "package test;",
            "import java.util.Map.Entry;",
            "public class Test {",
            "    Entry<String, String> e;",
            "    C c;",
            "    static class C {}",
            "}")
        .doTest();
  }

  @Test
  public void dontImportRuntime() {
    testHelper
        .addInputLines(
            "in/test/Test.java", //
            "package test;",
            "public class Test {",
            "    String s;",
            "}")
        .addOutputLines(
            "out/test/Test.java", //
            "package test;",
            "public class Test {",
            "    String s;",
            "}")
        .doTest();
  }

  @Test
  public void dontImportSelf() {
    testHelper
        .addInputLines(
            "in/test/Test.java",
            "package test;",
            "import java.util.*;",
            "public class Test {",
            "    Test s;",
            "}")
        .addOutputLines(
            "out/test/Test.java", //
            "package test;",
            "public class Test {",
            "    Test s;",
            "}")
        .doTest();
  }

  @Test
  public void dontImportSelfPrivate() {
    testHelper
        .addInputLines(
            "in/test/Test.java",
            "package test;",
            "import test.Test.Inner.*;",
            "public class Test {",
            "  public static class Inner {",
            "    private static class InnerMost {",
            "      InnerMost i;",
            "    }",
            "  }",
            "}")
        .addOutputLines(
            "out/test/Test.java",
            "package test;",
            "public class Test {",
            "  public static class Inner {",
            "    private static class InnerMost {",
            "      InnerMost i;",
            "    }",
            "  }",
            "}")
        .doTest();
  }

  @Test
  public void dontImportSelfNested() {
    testHelper
        .addInputLines(
            "in/test/Test.java",
            "package test;",
            "import java.util.*;",
            "public class Test {",
            "  public static class Inner {",
            "    Inner t;",
            "  }",
            "}")
        .addOutputLines(
            "out/test/Test.java",
            "package test;",
            "public class Test {",
            "  public static class Inner {",
            "    Inner t;",
            "  }",
            "}")
        .doTest();
  }

  @Test
  public void importSamePackage() {
    testHelper
        .addInputLines(
            "test/A.java",
            "package test;",
            "public class A {",
            "  public static class Inner {}",
            "}")
        .expectUnchanged()
        .addInputLines(
            "in/test/Test.java",
            "package test;",
            "import test.A.*;",
            "public class Test {",
            "  Inner t;",
            "}")
        .addOutputLines(
            "out/test/Test.java",
            "package test;",
            "import test.A.Inner;",
            "public class Test {",
            "  Inner t;",
            "}")
        .doTest();
  }

  @Test
  public void negativeNoWildcard() {
    CompilationTestHelper.newInstance(WildcardImport.class, getClass())
        .addSourceLines(
            "test/Test.java",
            "package test;",
            "import java.util.Map;",
            "public class Test {",
            "    Map.Entry<String, String> e;",
            "    C c;",
            "    static class C {}",
            "}")
        .doTest();
  }

  @Test
  public void sameUnitWithSpuriousWildImport() {
    testHelper
        .addInputLines(
            "in/test/Test.java",
            "package test;",
            "import java.util.Map;",
            "public class Test {",
            "    Map.Entry<String, String> e;",
            "    C c;",
            "    private static class C {}",
            "}")
        .addOutputLines(
            "test/Test.java",
            "package test;",
            "import java.util.Map;",
            "public class Test {",
            "    Map.Entry<String, String> e;",
            "    C c;",
            "    private static class C {}",
            "}")
        .doTest();
  }

  @Test
  public void nonCanonical() {
    testHelper
        .addInputLines(
            "a/One.java", //
            "package a;",
            "public class One extends Two {",
            "}")
        .expectUnchanged()
        .addInputLines(
            "a/Two.java", //
            "package a;",
            "public class Two {",
            "  public static class Inner {}",
            "}")
        .expectUnchanged()
        .addInputLines(
            "in/test/Test.java",
            "package test;",
            "import static a.One.*;",
            "public class Test {",
            "  Inner i;",
            "}")
        .addOutputLines(
            "out/test/Test.java",
            "package test;",
            "import a.Two.Inner;",
            "public class Test {",
            "  Inner i;",
            "}")
        .doTest();
  }

  @Test
  public void memberImport() {
    testHelper
        .addInputLines(
            "in/test/Test.java",
            "package test;",
            "import static java.util.Arrays.*;",
            "import java.util.*;",
            "public class Test {",
            "  List<Integer> xs = asList(1, 2, 3);",
            "}")
        .addOutputLines(
            "test/Test.java",
            "package test;",
            "import static java.util.Arrays.asList;",
            "import java.util.List;",
            "public class Test {",
            "  List<Integer> xs = asList(1, 2, 3);",
            "}")
        .doTest();
  }

  private static final Predicate<String> QUALIFY_MEMBERS_FIX =
      Predicates.and(
          Predicates.containsPattern("Wildcard imports.*should not be used"),
          Predicates.not(Predicates.contains(Pattern.compile("Did you mean"))));

  @Test
  public void qualifyMembersFix() {
    String[] enumLines = {
      "package e;",
      "public enum E {",
      "  A, B, C, D, E, F, G, H, I, J,",
      "  K, L, M, N, O, P, Q, R, S, T,",
      "  U, V, W, X, Y, Z",
      "}"
    };
    String[] testLines = {
      "// BUG: Diagnostic matches: X",
      "import static e.E.*;",
      "public class Test {",
      "  Object[] ex = {",
      "    A, B, C, D, E, F, G, H, I, J,",
      "    K, L, M, N, O, P, Q, R, S, T,",
      "    U, V, W, X, Y, Z",
      "  };",
      "  boolean f(e.E e) {",
      "    switch (e) {",
      "      case A:",
      "      case E:",
      "      case I:",
      "      case O:",
      "      case U:",
      "        return true;",
      "      default:",
      "        return false;",
      "    }",
      "  }",
      "}"
    };

    CompilationTestHelper.newInstance(WildcardImport.class, getClass())
        .addSourceLines("e/E.java", enumLines)
        .addSourceLines("e/Test.java", testLines)
        .expectErrorMessage("X", QUALIFY_MEMBERS_FIX)
        .doTest();

    testHelper
        .addInputLines("e/E.java", enumLines)
        .expectUnchanged()
        .addInputLines("in/Test.java", testLines)
        .addOutputLines(
            "out/Test.java",
            "import e.E;",
            "public class Test {",
            "  Object[] ex = {",
            "    E.A, E.B, E.C, E.D, E.E, E.F, E.G, E.H, E.I, E.J,",
            "    E.K, E.L, E.M, E.N, E.O, E.P, E.Q, E.R, E.S, E.T,",
            "    E.U, E.V, E.W, E.X, E.Y, E.Z",
            "  };",
            "  boolean f(e.E e) {",
            "    switch (e) {",
            "      case A:",
            "      case E:",
            "      case I:",
            "      case O:",
            "      case U:",
            "        return true;",
            "      default:",
            "        return false;",
            "    }",
            "  }",
            "}")
        .doTest();
  }

  @Test
  public void manyTypesFix() {

    ImmutableList<Character> alphabet =
        Stream.iterate('A', x -> (char) (x + 1)).limit(26).collect(toImmutableList());

    for (Character ch : alphabet) {
      testHelper =
          testHelper
              .addInputLines(
                  String.format("e/%s.java", ch),
                  String.format("package e; public class %s {}", ch))
              .expectUnchanged();
    }

    testHelper
        .addInputLines(
            "p/Test.java",
            "package p;",
            "import e.*;",
            "public class Test {",
            Streams.mapWithIndex(alphabet.stream(), (a, x) -> String.format("%s x%d;\n", a, x))
                .collect(joining("\n")),
            "}")
        .addOutputLines(
            "p/Test.java",
            "package p;",
            alphabet.stream().map(x -> String.format("import e.%s;", x)).collect(joining("\n")),
            "public class Test {",
            Streams.mapWithIndex(alphabet.stream(), (a, x) -> String.format("%s x%d;\n", a, x))
                .collect(joining("\n")),
            "}")
        .doTest();
  }
}
