/*
 * Copyright 2016 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.CompilationTestHelper;
import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.TemporaryFolder;
import org.junit.runner.RunWith;
import org.junit.runners.JUnit4;

@RunWith(JUnit4.class)
public class FunctionalInterfaceClashTest {

  @Rule public final TemporaryFolder tempFolder = new TemporaryFolder();

  private final CompilationTestHelper testHelper =
      CompilationTestHelper.newInstance(FunctionalInterfaceClash.class, getClass());

  @Test
  public void positive() {
    testHelper
        .addSourceLines(
            "Test.java", //
            "import java.util.function.Function;",
            "import java.util.function.Consumer;",
            "public class Test {",
            "  // BUG: Diagnostic contains: foo(Function<String, String>)",
            "  void foo(Consumer<String> x) {}",
            "  void foo(Function<String, String> c) {}",
            "}")
        .doTest();
  }

  @Test
  public void positiveNullary() {
    testHelper
        .addSourceLines(
            "Test.java", //
            "import java.util.concurrent.Callable;",
            "public class Test {",
            "  interface MyCallable {",
            "    String call();",
            "  }",
            "  // BUG: Diagnostic contains: foo(MyCallable)",
            "  void foo(Callable<String> x) {}",
            "  void foo(MyCallable c) {}",
            "}")
        .doTest();
  }

  @Test
  public void positiveInherited() {
    testHelper
        .addSourceLines(
            "Super.java", //
            "import java.util.function.Function;",
            "class Super {",
            "  void foo(Function<String, String> x) {}",
            "}")
        .addSourceLines(
            "Test.java", //
            "import java.util.function.Consumer;",
            "public class Test extends Super {",
            "  // BUG: Diagnostic contains: Super.foo(Function<String, String>)",
            "  void foo(Consumer<String> c) {}",
            "}")
        .doTest();
  }

  @Test
  public void positiveArgs() {
    testHelper
        .addSourceLines(
            "Test.java", //
            "import java.util.function.Function;",
            "import java.util.function.Consumer;",
            "public class Test {",
            "  // BUG: Diagnostic contains: foo(Function<String, Integer>)",
            "  void foo(Consumer<String> c) {}",
            "  void foo(Function<String, Integer> f) {}",
            "}")
        .doTest();
  }

  @Test
  public void negativeOverride() {
    testHelper
        .addSourceLines(
            "Super.java", //
            "import java.util.function.Consumer;",
            "class Super {",
            "  void foo(Consumer<String> x) {}",
            "}")
        .addSourceLines(
            "Test.java", //
            "import java.util.function.Consumer;",
            "public class Test extends Super {",
            "  void foo(Consumer<String> x) {}",
            "}")
        .doTest();
  }

  @Test
  public void negativeSuperConstructor() {
    testHelper
        .addSourceLines(
            "Super.java", //
            "import java.util.function.Function;",
            "class Super {",
            "  Super(Function<String, String> r) {}",
            "}")
        .addSourceLines(
            "Test.java", //
            "import java.util.function.Consumer;",
            "public class Test extends Super {",
            "  Test(Consumer<String> r) { super(null); }",
            "}")
        .doTest();
  }

  @Test
  public void positiveConstructor() {
    testHelper
        .addSourceLines(
            "Test.java", //
            "import java.util.function.Function;",
            "import java.util.function.Consumer;",
            "public class Test {",
            "  // BUG: Diagnostic contains: Test(Function<String, String>)",
            "  Test(Consumer<String> r) {}",
            "  Test(Function<String, String> c) {}",
            "}")
        .doTest();
  }

  @Test
  public void positiveStatic() {
    testHelper
        .addSourceLines(
            "Test.java", //
            "import java.util.function.Function;",
            "import java.util.function.Consumer;",
            "public class Test {",
            "  // BUG: Diagnostic contains: foo(Function<String, String>)",
            "  static void foo(Consumer<String> x) {}",
            "  void foo(Function<String, String> c) {}",
            "}")
        .doTest();
  }

  @Test
  public void suppressWarningsOnMethod() {
    testHelper
        .addSourceLines(
            "Test.java",
            "import java.lang.SuppressWarnings;",
            "import java.util.function.Function;",
            "import java.util.function.Consumer;",
            "public class Test {",
            "  @SuppressWarnings(\"FunctionalInterfaceClash\")",
            "  void foo(Consumer<String> x) {}",
            "  void foo(Function<String, String> c) {}",
            "}")
        .doTest();
  }

  @Test
  public void shouldIgnore_transitiveInheritanceWithExpandedVisibility() {
    testHelper
        .addSourceLines(
            "pkg1/FunctionalInterface.java",
            "package pkg1;",
            "public interface FunctionalInterface {",
            "  String apply(String s);",
            "}")
        .addSourceLines(
            "pkg2/BaseClass.java",
            "package pkg2;",
            "import pkg1.FunctionalInterface;",
            "public abstract class BaseClass {",
            "  abstract String doIt(FunctionalInterface fi);",
            "}")
        .addSourceLines(
            "pkg2/DerivedClass.java",
            "package pkg2;",
            "import pkg1.FunctionalInterface;",
            "public class DerivedClass extends BaseClass {",
            "  @Override public String doIt(FunctionalInterface fi) {",
            "    return null;",
            "  }",
            "}")
        .addSourceLines(
            "pkg3/Test.java",
            "package pkg3;",
            "import pkg1.FunctionalInterface;",
            "import pkg2.DerivedClass;",
            "public class Test {",
            "  DerivedClass getDerivedClass() {",
            "    return new DerivedClass() {",
            "      @Override public String doIt(FunctionalInterface fi) {",
            "        return null;",
            "      }",
            "    };",
            "  }",
            "}")
        .doTest();
  }

  @Test
  public void negative_multipleClashingOverriddenMethods() {
    testHelper
        .addSourceLines(
            "pkg2/BaseClass.java",
            "package pkg2;",
            "import java.util.function.Function;",
            "import java.util.function.Consumer;",
            "public abstract class BaseClass {",
            "  // BUG: Diagnostic contains: When passing lambda arguments to this function",
            "  abstract void baz(Consumer<String> c);",
            "  abstract void baz(Function<String, Integer> f);",
            "}")
        .addSourceLines(
            "pkg2/DerivedClass.java",
            "package pkg2;",
            "import java.util.function.Function;",
            "import java.util.function.Consumer;",
            "public class DerivedClass extends BaseClass {",
            "  @Override void baz(Consumer<String> c) {}",
            "  @Override void baz(Function<String, Integer> f) {}",
            "}")
        .doTest();
  }

  @Test
  public void negative_singleClashingOverriddenMethods() {
    testHelper
        .addSourceLines(
            "pkg2/BaseClass.java",
            "package pkg2;",
            "import java.util.function.Function;",
            "import java.util.function.Consumer;",
            "public abstract class BaseClass {",
            "  abstract void bar(Consumer<String> c);",
            "}")
        .addSourceLines(
            "pkg2/DerivedClass.java",
            "package pkg2;",
            "import java.util.function.Function;",
            "import java.util.function.Consumer;",
            "public class DerivedClass extends BaseClass {",
            "  @Override void bar(Consumer<String> c) {}",
            "}")
        .doTest();
  }

  @Test
  public void positive_overriddenAndNewClashingMethod() {
    testHelper
        .addSourceLines(
            "pkg2/BaseClass.java",
            "package pkg2;",
            "import java.util.function.Function;",
            "import java.util.function.Consumer;",
            "public class BaseClass {",
            "  void conduct(Consumer<String> c) {}",
            "}")
        .addSourceLines(
            "pkg2/ConductClass.java",
            "package pkg2;",
            "import java.util.function.Function;",
            "import java.util.function.Consumer;",
            "public class ConductClass extends BaseClass {",
            "  // BUG: Diagnostic contains: disambiguate with:",
            "  @Override void conduct(Consumer<String> c) {}",
            "  void conduct(Function<String, Integer> f) {}",
            "}")
        .doTest();
  }

  @Test
  public void negative_overriddenMethod() {
    testHelper
        .addSourceLines(
            "pkg2/BaseClass.java",
            "package pkg2;",
            "import java.util.function.Function;",
            "import java.util.function.Consumer;",
            "public class BaseClass {",
            "  void conduct(Consumer<String> c) {}",
            "}")
        .addSourceLines(
            "pkg2/ConductClass.java",
            "package pkg2;",
            "import java.util.function.Function;",
            "import java.util.function.Consumer;",
            "public class ConductClass extends BaseClass {",
            "  @Override void conduct(Consumer<String> c) {}",
            "}")
        .doTest();
  }

  @Test
  public void positive_overriddenInClassAndNewClashingMethod() {
    testHelper
        .addSourceLines(
            "pkg2/Super.java",
            "package pkg2;",
            "import java.util.function.Consumer;",
            "public abstract class Super {",
            "  void barr(Consumer<String> c) {}",
            "}")
        .addSourceLines(
            "pkg2/BaseClass.java",
            "package pkg2;",
            "import java.util.function.Consumer;",
            "public abstract class BaseClass extends Super {",
            "  void barr(Consumer<String> c) {}",
            "}")
        .addSourceLines(
            "pkg2/MyDerivedClass.java",
            "package pkg2;",
            "import java.util.function.Function;",
            "public class MyDerivedClass extends BaseClass {",
            "  // BUG: Diagnostic contains: disambiguate with:",
            "  void barr(Function<String, Integer> f) {}",
            "}")
        .doTest();
  }

  @Test
  public void positive_overridesInClassAndNewClashingPairInSameClass() {
    testHelper
        .addSourceLines(
            "pkg2/Super.java",
            "package pkg2;",
            "import java.util.function.Consumer;",
            "import java.util.function.Function;",
            "public abstract class Super {",
            "  // BUG: Diagnostic contains: When passing lambda arguments to this function",
            "  void barr(Function<String, Integer> f) {}",
            "  void barr(Consumer<String> c) {}",
            "}")
        .addSourceLines(
            "pkg2/BaseClass.java",
            "package pkg2;",
            "import java.util.function.Consumer;",
            "import java.util.function.Function;",
            "public abstract class BaseClass extends Super {",
            "  void barr(Function<String, Integer> f) {}",
            "  void barr(Consumer<String> c) {}",
            "  // BUG: Diagnostic contains: When passing lambda arguments to this function",
            "  void foo(Function<Integer, Integer> f) {}",
            "  void foo(Consumer<Integer> c) {}",
            "}")
        .doTest();
  }

  @Test
  public void oneIsMoreSpecific_notAmbiguous() {
    testHelper
        .addSourceLines(
            "Test.java",
            "import java.util.function.Consumer;",
            "public class Test {",
            "  void foo(Consumer<String> c) {}",
            "  void foo(SubConsumer<String> c) {}",
            "  interface SubConsumer<T> extends Consumer<T> {}",
            "}")
        .doTest();
  }
}
