/*
 * 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.Collections.sort;

import com.google.common.annotations.GwtCompatible;
import com.google.common.annotations.GwtIncompatible;
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.Arrays;
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;

/**
 * Unit test for {@link TreeMultiset}.
 *
 * @author Neal Kanodia
 */
@GwtCompatible(emulated = true)
public class TreeMultisetTest extends TestCase {

  @GwtIncompatible // suite
  public static Test suite() {
    TestSuite suite = new TestSuite();
    suite.addTest(SortedMultisetTestSuiteBuilder
        .using(new TestStringMultisetGenerator() {
          @Override
          protected Multiset<String> create(String[] elements) {
            return TreeMultiset.create(Arrays.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(Arrays.asList(elements)).elementSet();
        }

        @Override
        public List<String> order(List<String> insertionOrder) {
          return Lists.newArrayList(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(Arrays.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(Arrays.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(Arrays.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<String> comparator = Ordering.<String>natural().nullsFirst();
    TreeMultiset<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<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());
  }

  @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");
  }
}