/*
 * 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.BugCheckerRefactoringTestHelper;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.JUnit4;

/** Unit tests for {@link ImmutableSetForContains}. */
@RunWith(JUnit4.class)
public final class ImmutableSetForContainsTest {
  private final BugCheckerRefactoringTestHelper refactoringHelper =
      BugCheckerRefactoringTestHelper.newInstance(ImmutableSetForContains.class, getClass());

  @Test
  public void immutableListOf_onlyContainsReplaces() {
    refactoringHelper
        .addInputLines(
            "Test.java",
            """
            import static com.google.common.collect.ImmutableList.toImmutableList;
            import com.google.common.collect.ImmutableList;
            import java.util.ArrayList;

            class Test {
              private static final ImmutableList<String> MY_LIST_1 =
                  ImmutableList.<String>builder().add("hello").build();
              private static final ImmutableList<String> MY_LIST_2 = ImmutableList.of("hello");
              private static final ImmutableList<String> MY_LIST_3 =
                  new ArrayList<String>().stream().collect(toImmutableList());
              private static final ImmutableList<String> MY_LIST_4 =
                  new ImmutableList.Builder<String>().add("hello").build();

              private void myFunc() {
                boolean myBool1 = MY_LIST_1.contains("he");
                boolean myBool2 = MY_LIST_2.containsAll(new ArrayList<String>());
                boolean myBool3 = MY_LIST_3.isEmpty();
                boolean myBool4 = MY_LIST_4.isEmpty();
              }
            }
            """)
        .addOutputLines(
            "Test.java",
            """
            import static com.google.common.collect.ImmutableList.toImmutableList;
            import static com.google.common.collect.ImmutableSet.toImmutableSet;
            import com.google.common.collect.ImmutableList;
            import com.google.common.collect.ImmutableSet;
            import java.util.ArrayList;

            class Test {
              private static final ImmutableSet<String> MY_LIST_1 =
                  ImmutableSet.<String>builder().add("hello").build();
              private static final ImmutableSet<String> MY_LIST_2 = ImmutableSet.of("hello");
              private static final ImmutableSet<String> MY_LIST_3 =
                  new ArrayList<String>().stream().collect(toImmutableSet());
              private static final ImmutableSet<String> MY_LIST_4 =
                  new ImmutableSet.Builder<String>().add("hello").build();

              private void myFunc() {
                boolean myBool1 = MY_LIST_1.contains("he");
                boolean myBool2 = MY_LIST_2.containsAll(new ArrayList<String>());
                boolean myBool3 = MY_LIST_3.isEmpty();
                boolean myBool4 = MY_LIST_4.isEmpty();
              }
            }
            """)
        .doTest();
  }

  @Test
  public void immutableList_initUsingStaticFunc_replacesWithCopyOf() {
    refactoringHelper
        .addInputLines(
            "Test.java",
            """
            import static com.google.common.collect.ImmutableList.toImmutableList;
            import com.google.common.collect.ImmutableList;
            import java.util.ArrayList;

            class Test {
              private static final ImmutableList<String> MY_LIST = initMyList();

              private static ImmutableList<String> initMyList() {
                return ImmutableList.of();
              }

              private void myFunc() {
                boolean myBool = MY_LIST.contains("he");
              }
            }
            """)
        .addOutputLines(
            "Test.java",
"""
import static com.google.common.collect.ImmutableList.toImmutableList;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableSet;
import java.util.ArrayList;

class Test {
  private static final ImmutableSet<String> MY_LIST = ImmutableSet.copyOf(initMyList());

  private static ImmutableList<String> initMyList() {
    return ImmutableList.of();
  }

  private void myFunc() {
    boolean myBool = MY_LIST.contains("he");
  }
}
""")
        .doTest();
  }

  @Test
  public void immutableList_rawType_replacesWithImmutableSet() {
    refactoringHelper
        .addInputLines(
            "Test.java",
            """
            import com.google.common.collect.ImmutableList;
            import java.util.ArrayList;

            class Test {
              private static final ImmutableList MY_LIST = ImmutableList.of("hello");

              private void myFunc() {
                boolean myBool = MY_LIST.contains("he");
              }
            }
            """)
        .addOutputLines(
            "Test.java",
            """
            import com.google.common.collect.ImmutableList;
            import com.google.common.collect.ImmutableSet;
            import java.util.ArrayList;

            class Test {
              private static final ImmutableSet MY_LIST = ImmutableSet.of("hello");

              private void myFunc() {
                boolean myBool = MY_LIST.contains("he");
              }
            }
            """)
        .doTest();
  }

  @Test
  public void fieldAnnotatedWithBind_noMatch() {
    refactoringHelper
        .addInputLines(
            "Test.java",
            """
            import com.google.common.collect.ImmutableList;
            import com.google.inject.testing.fieldbinder.Bind;
            import java.util.ArrayList;

            class Test {
              @Bind private static final ImmutableList MY_LIST = ImmutableList.of("hello");

              private void myFunc() {
                boolean myBool = MY_LIST.contains("he");
              }
            }
            """)
        .expectUnchanged()
        .doTest();
  }

  @Test
  public void immutableVarPassedToAFunc_doesNothing() {
    refactoringHelper
        .addInputLines(
            "Test.java",
            """
            import com.google.common.collect.ImmutableList;

            class Test {
              private static final ImmutableList<String> MY_LIST = ImmutableList.of("hello");

              private void myFunc() {
                consumer(MY_LIST);
              }

              private void consumer(ImmutableList<String> arg) {}
            }
            """)
        .expectUnchanged()
        .doTest();
  }

  @Test
  public void immutableList_uniqueElements_iterating_negative() {
    refactoringHelper
        .addInputLines(
            "Test.java",
            """
            import static com.sun.source.tree.Tree.Kind.METHOD_INVOCATION;
            import com.google.common.collect.ImmutableList;
            import com.sun.source.tree.Tree.Kind;
            import java.util.ArrayList;

            class Test {
              private static final ImmutableList<String> STR_LIST = ImmutableList.of("hello");
              private static final ImmutableList<Kind> ENUM_LIST =
                  ImmutableList.of(Kind.AND, METHOD_INVOCATION);

              private void myFunc() {
                STR_LIST.stream().forEach(System.out::println);
                STR_LIST.forEach(System.out::println);
                ENUM_LIST.stream().forEach(System.out::println);
                ENUM_LIST.forEach(System.out::println);
                for (String myStr : STR_LIST) {
                  System.out.println(myStr);
                }
                for (Kind myKind : ENUM_LIST) {
                  System.out.println(myKind);
                }
                for (Long lvar : ImmutableList.<Long>of(2L)) {
                  System.out.println(lvar);
                }
                ImmutableList<Long> longList = ImmutableList.of(1L);
                for (Long lvar : longList) {
                  System.out.println(lvar);
                }
              }
            }
            """)
        .expectUnchanged()
        .doTest();
  }

  @Test
  public void immutableList_duplicateElements_iterating_doesNotReplace() {
    refactoringHelper
        .addInputLines(
            "Test.java",
"""
import com.google.common.collect.ImmutableList;
import java.util.ArrayList;

class Test {
  private static final ImmutableList<String> STR_LIST_1 = ImmutableList.of("hello", "hello");
  private static final ImmutableList<String> STR_LIST_2 = ImmutableList.of("hello", strGenFunc());

  private void myFunc() {
    STR_LIST_1.stream().forEach(System.out::println);
    STR_LIST_2.stream().forEach(System.out::println);
  }

  private static String strGenFunc() {
    return "";
  }
}
""")
        .expectUnchanged()
        .doTest();
  }

  @Test
  public void immutableList_distinctElementsInBuilder_iterating_doesNotReplace() {
    refactoringHelper
        .addInputLines(
            "Test.java",
            """
            import com.google.common.collect.ImmutableList;
            import java.util.ArrayList;

            class Test {
              private static final ImmutableList<String> STR_LIST =
                  ImmutableList.<String>builder().add("hello").build();

              private void myFunc() {
                STR_LIST.stream().forEach(System.out::println);
              }
            }
            """)
        .expectUnchanged()
        .doTest();
  }

  @Test
  public void immutableList_passedToFunctionsAcceptingSet_negative() {
    refactoringHelper
        .addInputLines(
            "Test.java",
            """
            import com.google.common.collect.ImmutableList;
            import com.google.common.collect.ImmutableSet;
            import com.google.common.collect.Iterables;
            import java.util.ArrayList;

            class Test {
              private static final ImmutableList<String> MY_LIST = ImmutableList.of("hello");

              private void myFunc() {
                ImmutableSet<String> mySet = ImmutableSet.copyOf(MY_LIST);
                String onlyElement = Iterables.getOnlyElement(MY_LIST);
              }
            }
            """)
        .expectUnchanged()
        .doTest();
  }

  @Test
  public void immutableList_passedToGenericFunctionAcceptingList_doesNothing() {
    refactoringHelper
        .addInputLines(
            "Test.java",
            """
            import com.google.common.collect.ImmutableList;
            import com.google.common.collect.ImmutableMap;
            import com.google.common.collect.ImmutableSet;
            import com.google.common.collect.Iterables;
            import java.util.ArrayList;
            import java.util.List;

            class Test {
              private static final ImmutableList<String> MY_LIST_1 = ImmutableList.of("hello");
              private static final ImmutableList<String> MY_LIST_2 = ImmutableList.of("hello");

              private void myFunc() {
                ImmutableMap<String, List<String>> myMap =
                    ImmutableMap.<String, List<String>>builder().put("a", MY_LIST_1).build();
                boolean myBool = ImmutableList.of().equals(MY_LIST_2);
              }
            }
            """)
        .expectUnchanged()
        .doTest();
  }

  @Test
  public void immutableList_uniqueElements_inLoopExprAndStatement_doesNothing() {
    refactoringHelper
        .addInputLines(
            "Test.java",
            """
            import com.google.common.collect.ImmutableList;

            class Test {
              private static final ImmutableList<String> MY_LIST = ImmutableList.of("hello");

              private void myFunc() {
                for (String myStr : MY_LIST) {
                  System.out.println(MY_LIST.indexOf(myStr));
                }
              }
            }
            """)
        .expectUnchanged()
        .doTest();
  }

  @Test
  public void immutableListInNestedClass_usedInParentClass_negative() {
    refactoringHelper
        .addInputLines(
            "Test.java",
            """
            import com.google.common.collect.ImmutableList;

            class Test {
              private static final class Nested {
                private static final ImmutableList<String> MY_LIST_1 = ImmutableList.of("a");
                private static final ImmutableList<String> MY_LIST_2 = ImmutableList.of("b");
              }

              private void myFunc() {
                String one = Nested.MY_LIST_1.get(0);
                String two = Nested.MY_LIST_2.iterator().next();
              }
            }
            """)
        .expectUnchanged()
        .doTest();
  }

  @Test
  public void twoImmutableListsUsedInSameMethodInvocation_negative() {
    refactoringHelper
        .addInputLines(
            "Test.java",
            """
            import com.google.common.collect.ImmutableList;
            import java.util.ArrayList;

            class Test {
              private static final ImmutableList<String> MY_LIST_1 = ImmutableList.of("hello");
              private static final ImmutableList<String> MY_LIST_2 = ImmutableList.of("world");

              private void myFunc() {
                MY_LIST_1.forEach(elem -> System.out.println(MY_LIST_2.get(0)));
              }
            }
            """)
        .expectUnchanged()
        .doTest();
  }

  @Test
  public void listOfClassOrDistinctInstances_uniqueElements_iterating_negative() {
    refactoringHelper
        .addInputLines(
            "Test.java",
            """
            import static com.sun.source.tree.Tree.Kind.METHOD_INVOCATION;
            import com.google.common.collect.ImmutableList;
            import com.sun.source.tree.Tree.Kind;
            import java.util.ArrayList;

            class Test {
              private static final ImmutableList<Class<?>> CLS_LIST =
                  ImmutableList.of(Long.class, Double.class);
              private static final ImmutableList<Object> OBJ_LIST =
                  ImmutableList.of(new String(""), new Object());

              private void myFunc() {
                CLS_LIST.stream().forEach(System.out::println);
                OBJ_LIST.forEach(System.out::println);
              }
            }
            """)
        .expectUnchanged()
        .doTest();
  }

  @Test
  public void immutableListGetInVarArg_doesNothing() {
    refactoringHelper
        .addInputLines(
            "Test.java",
            """
            import com.google.common.collect.ImmutableList;
            import java.util.ArrayList;

            class Test {
              private static final ImmutableList<String> MY_LIST = ImmutableList.of("hello");

              private String myFunc() {
                return String.format("%s", MY_LIST.get(0));
              }
            }
            """)
        .expectUnchanged()
        .doTest();
  }

  @Test
  public void suppressionOnVariableTree_noFinding() {
    refactoringHelper
        .addInputLines(
            "Test.java",
            """
            import com.google.common.collect.ImmutableList;

            class Test {
              @SuppressWarnings("ImmutableSetForContains")
              private static final ImmutableList<String> MY_LIST_1 =
                  ImmutableList.<String>builder().add("hello").build();

              private void myFunc() {
                boolean myBool1 = MY_LIST_1.contains("he");
              }
            }
            """)
        .expectUnchanged()
        .doTest();
  }

  @Test
  public void bothGetDisallowedAndContainsAllowed_noFinding() {
    refactoringHelper
        .addInputLines(
            "Test.java",
            """
            import com.google.common.collect.ImmutableList;

            class Test {
              private static final ImmutableList<String> MY_LIST_1 = ImmutableList.of("hello");

              private void myFunc() {
                String myString = MY_LIST_1.get(0);
                boolean myBool1 = MY_LIST_1.contains("he");
              }
            }
            """)
        .expectUnchanged()
        .doTest();
  }

  @Test
  public void bothContainsAllowedAndGetDisallowed_noFinding() {
    refactoringHelper
        .addInputLines(
            "Test.java",
            """
            import com.google.common.collect.ImmutableList;

            class Test {
              private static final ImmutableList<String> MY_LIST_1 = ImmutableList.of("hello");

              private void myFunc() {
                boolean myBool1 = MY_LIST_1.contains("he");
                String myString = MY_LIST_1.get(0);
              }
            }
            """)
        .expectUnchanged()
        .doTest();
  }

  @Test
  public void unusedVariable_noFinding() {
    refactoringHelper
        .addInputLines(
            "Test.java",
            """
            import com.google.common.collect.ImmutableList;

            class Test {
              private static final ImmutableList<String> MY_LIST = ImmutableList.of("hello");
            }
            """)
        .expectUnchanged()
        .doTest();
  }
}
