/*
 * Copyright (C) 2007 The Guava 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.common.collect;

import static com.google.common.collect.BoundType.CLOSED;
import static com.google.common.truth.Truth.assertThat;
import static java.util.Arrays.asList;
import static java.util.Collections.sort;

import com.google.common.annotations.GwtCompatible;
import com.google.common.annotations.GwtIncompatible;
import com.google.common.annotations.J2ktIncompatible;
import com.google.common.collect.testing.Helpers.NullsBeforeB;
import com.google.common.collect.testing.NavigableSetTestSuiteBuilder;
import com.google.common.collect.testing.TestStringSetGenerator;
import com.google.common.collect.testing.features.CollectionFeature;
import com.google.common.collect.testing.features.CollectionSize;
import com.google.common.collect.testing.google.MultisetFeature;
import com.google.common.collect.testing.google.SortedMultisetTestSuiteBuilder;
import com.google.common.collect.testing.google.TestStringMultisetGenerator;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;
import java.util.Set;
import java.util.SortedSet;
import junit.framework.Test;
import junit.framework.TestCase;
import junit.framework.TestSuite;
import org.jspecify.annotations.NullMarked;
import org.jspecify.annotations.Nullable;

/**
 * Unit test for {@link TreeMultiset}.
 *
 * @author Neal Kanodia
 */
@GwtCompatible
@NullMarked
public class TreeMultisetTest extends TestCase {

  @J2ktIncompatible
  @GwtIncompatible // suite
  @AndroidIncompatible // test-suite builders
  public static Test suite() {
    TestSuite suite = new TestSuite();
    suite.addTest(
        SortedMultisetTestSuiteBuilder.using(
                new TestStringMultisetGenerator() {
                  @Override
                  protected Multiset<String> create(String[] elements) {
                    return TreeMultiset.create(asList(elements));
                  }

                  @Override
                  public List<String> order(List<String> insertionOrder) {
                    return Ordering.natural().sortedCopy(insertionOrder);
                  }
                })
            .withFeatures(
                CollectionSize.ANY,
                CollectionFeature.KNOWN_ORDER,
                CollectionFeature.GENERAL_PURPOSE,
                CollectionFeature.SERIALIZABLE,
                CollectionFeature.ALLOWS_NULL_QUERIES,
                MultisetFeature.ENTRIES_ARE_VIEWS)
            .named("TreeMultiset, Ordering.natural")
            .createTestSuite());
    suite.addTest(
        SortedMultisetTestSuiteBuilder.using(
                new TestStringMultisetGenerator() {
                  @Override
                  protected Multiset<String> create(String[] elements) {
                    Multiset<String> result = TreeMultiset.create(NullsBeforeB.INSTANCE);
                    Collections.addAll(result, elements);
                    return result;
                  }

                  @Override
                  public List<String> order(List<String> insertionOrder) {
                    sort(insertionOrder, NullsBeforeB.INSTANCE);
                    return insertionOrder;
                  }
                })
            .withFeatures(
                CollectionSize.ANY,
                CollectionFeature.KNOWN_ORDER,
                CollectionFeature.GENERAL_PURPOSE,
                CollectionFeature.SERIALIZABLE,
                CollectionFeature.ALLOWS_NULL_VALUES,
                MultisetFeature.ENTRIES_ARE_VIEWS)
            .named("TreeMultiset, NullsBeforeB")
            .createTestSuite());
    suite.addTest(
        NavigableSetTestSuiteBuilder.using(
                new TestStringSetGenerator() {
                  @Override
                  protected Set<String> create(String[] elements) {
                    return TreeMultiset.create(asList(elements)).elementSet();
                  }

                  @Override
                  public List<String> order(List<String> insertionOrder) {
                    return new ArrayList<>(Sets.newTreeSet(insertionOrder));
                  }
                })
            .named("TreeMultiset[Ordering.natural].elementSet")
            .withFeatures(
                CollectionSize.ANY,
                CollectionFeature.REMOVE_OPERATIONS,
                CollectionFeature.ALLOWS_NULL_QUERIES)
            .createTestSuite());
    suite.addTestSuite(TreeMultisetTest.class);
    return suite;
  }

  public void testCreate() {
    TreeMultiset<String> multiset = TreeMultiset.create();
    multiset.add("foo", 2);
    multiset.add("bar");
    assertEquals(3, multiset.size());
    assertEquals(2, multiset.count("foo"));
    assertEquals(Ordering.natural(), multiset.comparator());
    assertEquals("[bar, foo x 2]", multiset.toString());
  }

  public void testCreateWithComparator() {
    Multiset<String> multiset = TreeMultiset.create(Collections.reverseOrder());
    multiset.add("foo", 2);
    multiset.add("bar");
    assertEquals(3, multiset.size());
    assertEquals(2, multiset.count("foo"));
    assertEquals("[foo x 2, bar]", multiset.toString());
  }

  public void testCreateFromIterable() {
    Multiset<String> multiset = TreeMultiset.create(asList("foo", "bar", "foo"));
    assertEquals(3, multiset.size());
    assertEquals(2, multiset.count("foo"));
    assertEquals("[bar, foo x 2]", multiset.toString());
  }

  public void testToString() {
    Multiset<String> ms = TreeMultiset.create();
    ms.add("a", 3);
    ms.add("c", 1);
    ms.add("b", 2);

    assertEquals("[a x 3, b x 2, c]", ms.toString());
  }

  public void testElementSetSortedSetMethods() {
    TreeMultiset<String> ms = TreeMultiset.create();
    ms.add("c", 1);
    ms.add("a", 3);
    ms.add("b", 2);
    SortedSet<String> elementSet = ms.elementSet();

    assertEquals("a", elementSet.first());
    assertEquals("c", elementSet.last());
    assertEquals(Ordering.natural(), elementSet.comparator());

    assertThat(elementSet.headSet("b")).containsExactly("a");
    assertThat(elementSet.tailSet("b")).containsExactly("b", "c").inOrder();
    assertThat(elementSet.subSet("a", "c")).containsExactly("a", "b").inOrder();
  }

  public void testElementSetSubsetRemove() {
    TreeMultiset<String> ms = TreeMultiset.create();
    ms.add("a", 1);
    ms.add("b", 3);
    ms.add("c", 2);
    ms.add("d", 1);
    ms.add("e", 3);
    ms.add("f", 2);

    SortedSet<String> elementSet = ms.elementSet();
    assertThat(elementSet).containsExactly("a", "b", "c", "d", "e", "f").inOrder();
    SortedSet<String> subset = elementSet.subSet("b", "f");
    assertThat(subset).containsExactly("b", "c", "d", "e").inOrder();

    assertTrue(subset.remove("c"));
    assertThat(elementSet).containsExactly("a", "b", "d", "e", "f").inOrder();
    assertThat(subset).containsExactly("b", "d", "e").inOrder();
    assertEquals(10, ms.size());

    assertFalse(subset.remove("a"));
    assertThat(elementSet).containsExactly("a", "b", "d", "e", "f").inOrder();
    assertThat(subset).containsExactly("b", "d", "e").inOrder();
    assertEquals(10, ms.size());
  }

  public void testElementSetSubsetRemoveAll() {
    TreeMultiset<String> ms = TreeMultiset.create();
    ms.add("a", 1);
    ms.add("b", 3);
    ms.add("c", 2);
    ms.add("d", 1);
    ms.add("e", 3);
    ms.add("f", 2);

    SortedSet<String> elementSet = ms.elementSet();
    assertThat(elementSet).containsExactly("a", "b", "c", "d", "e", "f").inOrder();
    SortedSet<String> subset = elementSet.subSet("b", "f");
    assertThat(subset).containsExactly("b", "c", "d", "e").inOrder();

    assertTrue(subset.removeAll(asList("a", "c")));
    assertThat(elementSet).containsExactly("a", "b", "d", "e", "f").inOrder();
    assertThat(subset).containsExactly("b", "d", "e").inOrder();
    assertEquals(10, ms.size());
  }

  public void testElementSetSubsetRetainAll() {
    TreeMultiset<String> ms = TreeMultiset.create();
    ms.add("a", 1);
    ms.add("b", 3);
    ms.add("c", 2);
    ms.add("d", 1);
    ms.add("e", 3);
    ms.add("f", 2);

    SortedSet<String> elementSet = ms.elementSet();
    assertThat(elementSet).containsExactly("a", "b", "c", "d", "e", "f").inOrder();
    SortedSet<String> subset = elementSet.subSet("b", "f");
    assertThat(subset).containsExactly("b", "c", "d", "e").inOrder();

    assertTrue(subset.retainAll(asList("a", "c")));
    assertThat(elementSet).containsExactly("a", "c", "f").inOrder();
    assertThat(subset).containsExactly("c");
    assertEquals(5, ms.size());
  }

  public void testElementSetSubsetClear() {
    TreeMultiset<String> ms = TreeMultiset.create();
    ms.add("a", 1);
    ms.add("b", 3);
    ms.add("c", 2);
    ms.add("d", 1);
    ms.add("e", 3);
    ms.add("f", 2);

    SortedSet<String> elementSet = ms.elementSet();
    assertThat(elementSet).containsExactly("a", "b", "c", "d", "e", "f").inOrder();
    SortedSet<String> subset = elementSet.subSet("b", "f");
    assertThat(subset).containsExactly("b", "c", "d", "e").inOrder();

    subset.clear();
    assertThat(elementSet).containsExactly("a", "f").inOrder();
    assertThat(subset).isEmpty();
    assertEquals(3, ms.size());
  }

  public void testCustomComparator() throws Exception {
    Comparator<String> comparator =
        new Comparator<String>() {
          @Override
          public int compare(String o1, String o2) {
            return o2.compareTo(o1);
          }
        };
    TreeMultiset<String> ms = TreeMultiset.create(comparator);

    ms.add("b");
    ms.add("c");
    ms.add("a");
    ms.add("b");
    ms.add("d");

    assertThat(ms).containsExactly("d", "c", "b", "b", "a").inOrder();

    SortedSet<String> elementSet = ms.elementSet();
    assertEquals("d", elementSet.first());
    assertEquals("a", elementSet.last());
    assertEquals(comparator, elementSet.comparator());
  }

  public void testNullAcceptingComparator() throws Exception {
    Comparator<@Nullable String> comparator = Ordering.<String>natural().<String>nullsFirst();
    TreeMultiset<@Nullable String> ms = TreeMultiset.create(comparator);

    ms.add("b");
    ms.add(null);
    ms.add("a");
    ms.add("b");
    ms.add(null, 2);

    assertThat(ms).containsExactly(null, null, null, "a", "b", "b").inOrder();
    assertEquals(3, ms.count(null));

    SortedSet<@Nullable String> elementSet = ms.elementSet();
    assertEquals(null, elementSet.first());
    assertEquals("b", elementSet.last());
    assertEquals(comparator, elementSet.comparator());
  }

  private static final Comparator<String> DEGENERATE_COMPARATOR =
      new Comparator<String>() {
        @Override
        public int compare(String o1, String o2) {
          return o1.length() - o2.length();
        }
      };

  /** Test a TreeMultiset with a comparator that can return 0 when comparing unequal values. */
  public void testDegenerateComparator() throws Exception {
    TreeMultiset<String> ms = TreeMultiset.create(DEGENERATE_COMPARATOR);

    ms.add("foo");
    ms.add("a");
    ms.add("bar");
    ms.add("b");
    ms.add("c");

    assertEquals(2, ms.count("bar"));
    assertEquals(3, ms.count("b"));

    Multiset<String> ms2 = TreeMultiset.create(DEGENERATE_COMPARATOR);

    ms2.add("cat", 2);
    ms2.add("x", 3);

    assertEquals(ms, ms2);
    assertEquals(ms2, ms);

    SortedSet<String> elementSet = ms.elementSet();
    assertEquals("a", elementSet.first());
    assertEquals("foo", elementSet.last());
    assertEquals(DEGENERATE_COMPARATOR, elementSet.comparator());
  }

  public void testSubMultisetSize() {
    TreeMultiset<String> ms = TreeMultiset.create();
    ms.add("a", Integer.MAX_VALUE);
    ms.add("b", Integer.MAX_VALUE);
    ms.add("c", 3);

    assertEquals(Integer.MAX_VALUE, ms.count("a"));
    assertEquals(Integer.MAX_VALUE, ms.count("b"));
    assertEquals(3, ms.count("c"));

    assertEquals(Integer.MAX_VALUE, ms.headMultiset("c", CLOSED).size());
    assertEquals(Integer.MAX_VALUE, ms.headMultiset("b", CLOSED).size());
    assertEquals(Integer.MAX_VALUE, ms.headMultiset("a", CLOSED).size());

    assertEquals(3, ms.tailMultiset("c", CLOSED).size());
    assertEquals(Integer.MAX_VALUE, ms.tailMultiset("b", CLOSED).size());
    assertEquals(Integer.MAX_VALUE, ms.tailMultiset("a", CLOSED).size());
  }

  @J2ktIncompatible
  @GwtIncompatible // reflection
  @AndroidIncompatible // Reflection bug, or actual binary compatibility problem?
  public void testElementSetBridgeMethods() {
    for (Method m : TreeMultiset.class.getMethods()) {
      if (m.getName().equals("elementSet") && m.getReturnType().equals(SortedSet.class)) {
        return;
      }
    }
    fail("No bridge method found");
  }
}
