/*
 * Copyright 2019 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.time;

import com.google.errorprone.CompilationTestHelper;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.JUnit4;

/** Tests for {@link PreferJavaTimeOverload}. */
@RunWith(JUnit4.class)
public class PreferJavaTimeOverloadTest {
  private final CompilationTestHelper helper =
      CompilationTestHelper.newInstance(PreferJavaTimeOverload.class, getClass());

  @Test
  public void callingLongTimeUnitMethodWithDurationOverload_microseconds() {
    helper
        .addSourceLines(
            "TestClass.java",
            "import com.google.common.cache.CacheBuilder;",
            "import java.util.concurrent.TimeUnit;",
            "public class TestClass {",
            "  public CacheBuilder foo(CacheBuilder builder) {",
            "    // BUG: Diagnostic contains: builder.expireAfterAccess(Duration.of(42,"
                + " ChronoUnit.MICROS));",
            "    return builder.expireAfterAccess(42, TimeUnit.MICROSECONDS);",
            "  }",
            "}")
        .doTest();
  }

  @Test
  public void callingLongTimeUnitMethodWithDurationOverload() {
    helper
        .addSourceLines(
            "TestClass.java",
            "import com.google.common.cache.CacheBuilder;",
            "import java.util.concurrent.TimeUnit;",
            "public class TestClass {",
            "  public CacheBuilder foo(CacheBuilder builder) {",
            "    // BUG: Diagnostic contains: builder.expireAfterAccess(Duration.ofSeconds(42L));",
            "    return builder.expireAfterAccess(42L, TimeUnit.SECONDS);",
            "  }",
            "}")
        .doTest();
  }

  @Test
  public void callingLongTimeUnitMethodWithDurationOverload_durationDecompose() {
    helper
        .addSourceLines(
            "TestClass.java",
            "import com.google.common.cache.CacheBuilder;",
            "import java.time.Duration;",
            "import java.util.concurrent.TimeUnit;",
            "public class TestClass {",
            "  public CacheBuilder foo(CacheBuilder builder) {",
            "    Duration duration = Duration.ofMillis(12345);",
            "    // BUG: Diagnostic contains: builder.expireAfterAccess(duration);",
            "    return builder.expireAfterAccess(duration.getSeconds(), TimeUnit.SECONDS);",
            "  }",
            "}")
        .doTest();
  }

  @Test
  public void callingLongTimeUnitMethodWithDurationOverload_durationHashCode() {
    // this is admittedly a _very_ weird case, but we should _not_ suggest re-writing to:
    // builder.expireAfterAccess(duration)
    helper
        .addSourceLines(
            "TestClass.java",
            "import com.google.common.cache.CacheBuilder;",
            "import java.time.Duration;",
            "import java.util.concurrent.TimeUnit;",
            "public class TestClass {",
            "  public CacheBuilder foo(CacheBuilder builder) {",
            "    Duration duration = Duration.ofMillis(12345);",
            "    // BUG: Diagnostic contains: return"
                + " builder.expireAfterAccess(Duration.ofSeconds(duration.hashCode()));",
            "    return builder.expireAfterAccess(duration.hashCode(), TimeUnit.SECONDS);",
            "  }",
            "}")
        .doTest();
  }

  @Test
  public void callingLongTimeUnitMethodWithDurationOverload_intParam() {
    helper
        .addSourceLines(
            "TestClass.java",
            "import com.google.common.cache.CacheBuilder;",
            "import java.util.concurrent.TimeUnit;",
            "public class TestClass {",
            "  public CacheBuilder foo(CacheBuilder builder) {",
            "    // BUG: Diagnostic contains: builder.expireAfterAccess(Duration.ofSeconds(42));",
            "    return builder.expireAfterAccess(42, TimeUnit.SECONDS);",
            "  }",
            "}")
        .doTest();
  }

  @Test
  public void callLongTimeUnitInsideImpl() {
    helper
        .addSourceLines(
            "TestClass.java",
            "import java.time.Duration;",
            "import java.util.concurrent.TimeUnit;",
            "public class TestClass {",
            "  private void bar(long d, TimeUnit u) {",
            "  }",
            "  private void bar(Duration d) {",
            //  Would normally flag, but we're avoiding recursive suggestions
            "    bar(d.toMillis(), TimeUnit.MILLISECONDS);",
            "  }",
            "}")
        .doTest();
  }

  @Test
  public void callingLongTimeUnitMethodWithDurationOverload_privateMethod() {
    helper
        .addSourceLines(
            "TestClass.java",
            "import java.time.Duration;",
            "import java.util.concurrent.TimeUnit;",
            "public class TestClass {",
            "  private void bar(long v, TimeUnit tu) {",
            "  }",
            "  private void bar(Duration d) {",
            "  }",
            "  public void foo() {",
            "    // BUG: Diagnostic contains: bar(Duration.ofSeconds(42L));",
            "    bar(42L, TimeUnit.SECONDS);",
            "  }",
            "}")
        .doTest();
  }

  @Test
  public void callingLongTimeUnitMethodWithoutDurationOverload() {
    helper
        .addSourceLines(
            "TestClass.java",
            "import java.util.concurrent.Future;",
            "import java.util.concurrent.TimeUnit;",
            "public class TestClass {",
            "  public String foo(Future<String> future) throws Exception {",
            "    return future.get(42L, TimeUnit.SECONDS);",
            "  }",
            "}")
        .doTest();
  }

  @Test
  public void callingJodaDurationMethodWithDurationOverload_privateMethod() {
    helper
        .addSourceLines(
            "TestClass.java",
            "import java.time.Duration;",
            "public class TestClass {",
            "  private void bar(org.joda.time.Duration d) {",
            "  }",
            "  private void bar(Duration d) {",
            "  }",
            "  public void foo(org.joda.time.Duration jodaDuration) {",
            "    // BUG: Diagnostic contains: bar(Duration.ofMillis(jodaDuration.getMillis()));",
            "    bar(jodaDuration);",
            "  }",
            "}")
        .doTest();
  }

  @Test
  public void callingJodaInstantMethodWithInstantOverload_privateMethod() {
    helper
        .addSourceLines(
            "TestClass.java",
            "import java.time.Instant;",
            "public class TestClass {",
            "  private void bar(org.joda.time.Instant i) {",
            "  }",
            "  private void bar(Instant i) {",
            "  }",
            "  public void foo(org.joda.time.Instant jodaInstant) {",
            "    // BUG: Diagnostic contains: bar(Instant.ofEpochMilli(jodaInstant.getMillis()));",
            "    bar(jodaInstant);",
            "  }",
            "}")
        .doTest();
  }

  @Test
  public void callingJodaDurationMethodWithDurationOverload_privateMethod_jodaDurationMillis() {
    helper
        .addSourceLines(
            "TestClass.java",
            "import java.time.Duration;",
            "public class TestClass {",
            "  private void bar(org.joda.time.Duration d) {",
            "  }",
            "  private void bar(Duration d) {",
            "  }",
            "  public void foo() {",
            "    // BUG: Diagnostic contains: bar(Duration.ofMillis(42));",
            "    bar(org.joda.time.Duration.millis(42));",
            "  }",
            "}")
        .doTest();
  }

  @Test
  public void callingJodaDurationMethodWithDurationOverload_privateMethod_jodaDurationCtor() {
    helper
        .addSourceLines(
            "TestClass.java",
            "import java.time.Duration;",
            "public class TestClass {",
            "  private void bar(org.joda.time.Duration d) {",
            "  }",
            "  private void bar(Duration d) {",
            "  }",
            "  public void foo() {",
            "    // BUG: Diagnostic contains: bar(Duration.ofMillis(42));",
            "    bar(new org.joda.time.Duration(42));",
            "  }",
            "}")
        .doTest();
  }

  @Test
  public void callingJodaInstantMethodWithInstantOverload_privateMethod_jodaInstantCtor() {
    helper
        .addSourceLines(
            "TestClass.java",
            "import java.time.Instant;",
            "public class TestClass {",
            "  private void bar(org.joda.time.Instant i) {",
            "  }",
            "  private void bar(Instant i) {",
            "  }",
            "  public void foo() {",
            "    // BUG: Diagnostic contains: bar(Instant.ofEpochMilli(42));",
            "    bar(new org.joda.time.Instant(42));",
            "  }",
            "}")
        .doTest();
  }

  @Test
  public void callingJodaDurationMethodWithDurationOverload_privateMethod_jodaSeconds() {
    helper
        .addSourceLines(
            "TestClass.java",
            "import java.time.Duration;",
            "public class TestClass {",
            "  private void bar(org.joda.time.Duration d) {",
            "  }",
            "  private void bar(Duration d) {",
            "  }",
            "  public void foo() {",
            "    // BUG: Diagnostic contains: bar(Duration.ofSeconds(42));",
            "    bar(org.joda.time.Duration.standardSeconds(42));",
            "  }",
            "}")
        .doTest();
  }

  @Test
  public void callingJodaDurationMethodWithoutDurationOverload() {
    helper
        .addSourceLines(
            "TestClass.java",
            "public class TestClass {",
            "  private void bar(org.joda.time.Duration d) {",
            "  }",
            "  public void foo(org.joda.time.Duration jodaDuration) {",
            "    bar(jodaDuration);",
            "  }",
            "}")
        .doTest();
  }

  @Test
  public void callingJodaDurationMethodWithSupertypeJavaDurationOverload() {
    helper
        .addSourceLines(
            "TestClass.java",
            "public class TestClass extends SuperClass {",
            "  @Override",
            "  public void bar(java.time.Duration d) {",
            "    bar(org.joda.time.Duration.standardSeconds(d.getSeconds()));",
            "  }",
            "  public void bar(org.joda.time.Duration jodaDuration) {}",
            "}",
            "class SuperClass {",
            "  public void bar(java.time.Duration d) {}",
            "}")
        .doTest();
  }

  @Test
  public void callingJodaReadableInstantMethodWithInstantOverload_privateMethod() {
    helper
        .addSourceLines(
            "TestClass.java",
            "import java.time.Instant;",
            "public class TestClass {",
            "  private void bar(org.joda.time.ReadableInstant i) {",
            "  }",
            "  private void bar(Instant i) {",
            "  }",
            "  public void foo(org.joda.time.Instant jodaInstant) {",
            "    // BUG: Diagnostic contains: bar(Instant.ofEpochMilli(jodaInstant.getMillis()));",
            "    bar(jodaInstant);",
            "  }",
            "}")
        .doTest();
  }

  @Test
  public void callingJodaReadableDurationMethodWithoutDurationOverload() {
    helper
        .addSourceLines(
            "TestClass.java",
            "public class TestClass {",
            "  private void bar(org.joda.time.ReadableDuration d) {",
            "  }",
            "  public void foo(org.joda.time.Duration jodaDuration) {",
            "    bar(jodaDuration);",
            "  }",
            "}")
        .doTest();
  }

  @Test
  public void callingPrimitiveOverloadFromFieldInitializer() {
    helper
        .addSourceLines(
            "TestClass.java",
            "public class TestClass {",
            "  static {",
            "    // BUG: Diagnostic contains: call bar(Duration) instead",
            "    bar(42);",
            "  }",
            "  private static void bar(java.time.Duration d) {",
            "  }",
            "  private static void bar(long d) {",
            "  }",
            "}")
        .doTest();
  }

  @Test
  public void callingNumericPrimitiveMethodWithDurationOverload() {
    helper
        .addSourceLines(
            "TestClass.java",
            "public class TestClass {",
            "  private void bar(java.time.Duration d) {",
            "  }",
            "  private void bar(long d) {",
            "  }",
            "  public void foo() {",
            "    // BUG: Diagnostic contains: call bar(Duration) instead",
            "    bar(42);",
            "  }",
            "}")
        .doTest();
  }

  @Test
  public void callingNumericPrimitiveMethodWithInstantOverload() {
    helper
        .addSourceLines(
            "TestClass.java",
            "public class TestClass {",
            "  private void bar(java.time.Instant i) {",
            "  }",
            "  private void bar(long timestamp) {",
            "  }",
            "  public void foo() {",
            "    // BUG: Diagnostic contains: call bar(Instant) instead",
            "    bar(42);",
            "  }",
            "}")
        .doTest();
  }

  @Test
  public void ignoredApisAreExcluded() {}

  @Test
  public void b138221392() {}

  @Test
  public void durationDividedBy() {
    // See https://github.com/google/error-prone/issues/1431
    helper
        .addSourceLines(
            "TestClass.java",
            "import java.time.Duration;",
            "public class TestClass {",
            "  public static Duration dividedBy(long divisor) {",
            "    return Duration.ZERO;",
            "  }",
            "  public static long dividedBy(Duration divisor) {",
            "    return 0L;",
            "  }",
            "  public void foo() {",
            "    dividedBy(42L);",
            "    dividedBy(Duration.ZERO);",
            "  }",
            "}")
        .doTest();
  }

  // TODO(kak): After upgrading to AssertJ 3.15, double-check that removing the AssertJ matcher from
  // PreferJavaTimeOverload.IGNORED_APIS causes these tests to start failing.
  @Test
  public void assertJ() {
    // https://github.com/google/error-prone/issues/1377
    // https://github.com/google/error-prone/issues/1437
    helper
        .addSourceLines(
            "TestClass.java",
            "public class TestClass {",
            "  public void testAssertThat() {",
            "    org.assertj.core.api.Assertions.assertThat(1).isEqualTo(1);",
            "  }",
            "  public void testAssumeThat() {",
            "    org.assertj.core.api.Assumptions.assumeThat(1).isEqualTo(1);",
            "  }",
            "}")
        .doTest();
  }
}
