/*
 * Copyright 2018 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 com.google.errorprone.BugCheckerRefactoringTestHelper;
import com.google.errorprone.CompilationTestHelper;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.JUnit4;

/** Unit tests for {@link TypeNameShadowing} */
@RunWith(JUnit4.class)
public class TypeNameShadowingTest {

  private final CompilationTestHelper compilationHelper =
      CompilationTestHelper.newInstance(TypeNameShadowing.class, getClass());
  private final BugCheckerRefactoringTestHelper refactoring =
      BugCheckerRefactoringTestHelper.newInstance(TypeNameShadowing.class, getClass());

  @Test
  public void positiveClass() {
    compilationHelper
        .addSourceLines(
            "T.java",
            """
            package foo.bar;

            class T {}
            """)
        .addSourceLines(
            "Foo.java",
            """
            package foo.bar;

            // BUG: Diagnostic contains: Type parameter T shadows visible type foo.bar.T
            class Foo<T> {
              void bar(T t) {}
            }
            """)
        .doTest();
  }

  @Test
  public void positiveNestedClass() {
    compilationHelper
        .addSourceLines(
            "Foo.java",
            """
            package foo.bar;

            class Foo {
              class T {}

              // BUG: Diagnostic contains: Type parameter T shadows visible type foo.bar.Foo$T
              class Bar<T> {
                void bar(T t) {}
              }
            }
            """)
        .doTest();
  }

  @Test
  public void positiveNestedStaticClass() {
    compilationHelper
        .addSourceLines(
            "Foo.java",
            """
            package foo.bar;

            class Foo {
              public static class T {}

              // BUG: Diagnostic contains: Type parameter T shadows visible type foo.bar.Foo$T
              class Bar<T> {
                void bar(T t) {}
              }
            }
            """)
        .doTest();
  }

  @Test
  public void positiveNestedGeneric() {
    compilationHelper
        .addSourceLines(
            "T.java",
            """
            package foo.bar;

            interface T {}
            """)
        .addSourceLines(
            "Foo.java",
            """
            package foo.bar;

            class Foo {
              // BUG: Diagnostic contains: Type parameter T shadows visible type foo.bar.T
              class FooInner<T> {
                void bar(T t) {}
              }
            }
            """)
        .doTest();
  }

  @Test
  public void positiveOtherNestedClass() {
    compilationHelper
        .addSourceLines(
            "Foo.java",
            """
            package foo.bar;

            class Foo {
              class T {}
            }
            """)
        .addSourceLines(
            "Bar.java",
            """
            package foo.bar;

            import foo.bar.Foo.T;

            // BUG: Diagnostic contains: Type parameter T shadows visible type foo.bar.Foo$T
            class Bar<T> {
              void bar(T t) {}
            }
            """)
        .doTest();
  }

  @Test
  public void positiveMultipleParamsOneCollides() {
    compilationHelper
        .addSourceLines(
            "T.java",
            """
            package foo.bar;

            class T {}
            """)
        .addSourceLines(
            "Foo.java",
            """
            package foo.bar;

            // BUG: Diagnostic contains: Type parameter T shadows visible type foo.bar.T
            class Foo<T, U, V> {
              void bar(T t, U u, V v) {}
            }
            """)
        .doTest();
  }

  @Test
  public void positiveMultipleParamsBothCollide() {
    compilationHelper
        .addSourceLines(
            "T.java",
            """
            package foo.bar;

            class T {}
            """)
        .addSourceLines(
            "U.java",
            """
            package foo.bar;

            class U {}
            """)
        .addSourceLines(
            "Foo.java",
            """
            package foo.bar;

            // BUG: Diagnostic matches: combo
            class Foo<T, U> {
              void bar(T t, U u) {}
            }
            """)
        .expectErrorMessage(
            "combo",
            s ->
                s.contains("Type parameter T shadows visible type foo.bar.T")
                    && s.contains("Type parameter U shadows visible type foo.bar.U"))
        .doTest();
  }

  @Test
  public void positiveJavaLangCollision() {
    compilationHelper
        .addSourceLines(
            "Foo.java",
            """
            // BUG: Diagnostic contains: Class shadows visible type java.lang.Class
            class Foo<Class> {
              void bar(Class c) {}
            }
            """)
        .doTest();
  }

  @Test
  public void negativeClass() {
    compilationHelper
        .addSourceLines(
            "T.java",
            """
            package foo.bar;

            class T {}
            """)
        .addSourceLines(
            "Foo.java",
            """
            package foo.bar;

            class Foo<T1> {
              void bar(T1 t) {}
            }
            """)
        .doTest();
  }

  @Test
  public void negativeNestedClass() {
    compilationHelper
        .addSourceLines(
            "T.java",
            """
            package foo.bar;

            class T {
              class Foo<T1> {
                void bar(T1 t) {}
              }
            }
            """)
        .doTest();
  }

  @Test
  public void negativeOtherNestedClass() {
    compilationHelper
        .addSourceLines(
            "Foo.java",
            """
            package foo.bar;

            class Foo {
              class T {}
            }
            """)
        .addSourceLines(
            "Bar.java",
            """
            package foo.bar;

            class Bar<T> {
              void bar(T t) {}
            }
            """)
        .doTest();
  }

  @Test
  public void negativeStarImport() {
    compilationHelper
        .addSourceLines(
            "a/T.java",
            """
            package a;

            class T {}
            """)
        .addSourceLines(
            "b/Foo.java",
            """
            package b;

            import a.*;

            class Foo<T> {
              void bar(T u) {}
            }
            """)
        .doTest();
  }

  @Test
  public void refactorSingle() {
    refactoring
        .addInputLines(
            "Foo.java",
            """
            class Foo {
              class T {}

              <T> void f(T t) {}
            }
            """)
        .addOutputLines(
            "Foo.java",
            """
            class Foo {
              class T {}

              <T2> void f(T2 t) {}
            }
            """)
        .doTest();
  }

  @Test
  public void refactorMultiple() {
    refactoring
        .addInputLines(
            "in/Foo.java",
            """
            class Foo {
              class T {}

              class U {}

              <T, U> void f(T t, U u) {}
            }
            """)
        .addOutputLines(
            "out/Foo.java",
            """
            class Foo {
              class T {}

              class U {}

              <T2, U2> void f(T2 t, U2 u) {}
            }
            """)
        .doTest();
  }

  /**
   * Tests that we only suggest a fix when the type variable name is style-guide-adherent;
   * otherwise, it is likely the developer did not mean to use a generic and the fix is misleading
   * Input program here is alpha-equivalent to the refactorMultiple test case; output only fixes T
   */
  @Test
  public void fixOnlyWellNamedVariables() {
    refactoring
        .addInputLines(
            "in/Foo.java",
            """
            class Foo {
              class T {}

              class BadParameterName {}

              <T, BadParameterName> void f(T t, BadParameterName u) {}
            }
            """)
        .addOutputLines(
            "out/Foo.java",
            """
            class Foo {
              class T {}

              class BadParameterName {}

              <T2, BadParameterName> void f(T2 t, BadParameterName u) {}
            }
            """)
        .doTest();
  }

  @Test
  public void fieldClashOk() {
    compilationHelper
        .addSourceLines(
            "Test.java",
            """
            class Test {
              final Object T = new Object();

              <T> void doIt(T t) {}
            }
            """)
        .doTest();
  }
}
