/*
 * 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.collect.Lists.newArrayList;
import static com.google.common.collect.testing.IteratorFeature.MODIFIABLE;
import static org.junit.contrib.truth.Truth.ASSERT;

import java.util.Arrays;
import java.util.Collections;
import java.util.Comparator;
import java.util.Iterator;
import java.util.List;
import java.util.Set;
import java.util.SortedSet;

import com.google.common.annotations.GwtCompatible;
import com.google.common.annotations.GwtIncompatible;
import com.google.common.collect.testing.IteratorTester;

/**
 * Unit test for {@link TreeMultiset}.
 *
 * @author Neal Kanodia
 */
@GwtCompatible(emulated = true)
public class TreeMultisetTest extends AbstractMultisetTest {
  @SuppressWarnings("unchecked")
  @Override protected <E> Multiset<E> create() {
    return (Multiset) TreeMultiset.create();
  }

  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() {
    ms.add("a", 3);
    ms.add("c", 1);
    ms.add("b", 2);

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

  @GwtIncompatible("unreasonable slow")
  public void testIteratorBashing() {
    IteratorTester<String> tester =
        new IteratorTester<String>(createSample().size() + 2, MODIFIABLE,
            newArrayList(createSample()),
            IteratorTester.KnownOrder.KNOWN_ORDER) {
          private Multiset<String> targetMultiset;

          @Override protected Iterator<String> newTargetIterator() {
            targetMultiset = createSample();
            return targetMultiset.iterator();
          }

          @Override protected void verify(List<String> elements) {
            assertEquals(elements, Lists.newArrayList(targetMultiset));
          }
        };

    /* This next line added as a stopgap until JDK6 bug is fixed. */
    tester.ignoreSunJavaBug6529795();

    tester.test();
  }

  @GwtIncompatible("slow (~30s)")
  public void testElementSetIteratorBashing() {
    IteratorTester<String> tester = new IteratorTester<String>(5, MODIFIABLE,
        newArrayList("a", "b", "c"), IteratorTester.KnownOrder.KNOWN_ORDER) {
      private Set<String> targetSet;
      @Override protected Iterator<String> newTargetIterator() {
        Multiset<String> multiset = create();
        multiset.add("a", 3);
        multiset.add("c", 1);
        multiset.add("b", 2);
        targetSet = multiset.elementSet();
        return targetSet.iterator();
      }
      @Override protected void verify(List<String> elements) {
        assertEquals(elements, Lists.newArrayList(targetSet));
      }
    };

    /* This next line added as a stopgap until JDK6 bug is fixed. */
    tester.ignoreSunJavaBug6529795();

    tester.test();
  }

  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());

    ASSERT.that(elementSet.headSet("b")).hasContentsInOrder("a");
    ASSERT.that(elementSet.tailSet("b")).hasContentsInOrder("b", "c");
    ASSERT.that(elementSet.subSet("a", "c")).hasContentsInOrder("a", "b");
  }

  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();
    ASSERT.that(elementSet).hasContentsInOrder("a", "b", "c", "d", "e", "f");
    SortedSet<String> subset = elementSet.subSet("b", "f");
    ASSERT.that(subset).hasContentsInOrder("b", "c", "d", "e");

    assertTrue(subset.remove("c"));
    ASSERT.that(elementSet).hasContentsInOrder("a", "b", "d", "e", "f");
    ASSERT.that(subset).hasContentsInOrder("b", "d", "e");
    assertEquals(10, ms.size());

    assertFalse(subset.remove("a"));
    ASSERT.that(elementSet).hasContentsInOrder("a", "b", "d", "e", "f");
    ASSERT.that(subset).hasContentsInOrder("b", "d", "e");
    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();
    ASSERT.that(elementSet).hasContentsInOrder("a", "b", "c", "d", "e", "f");
    SortedSet<String> subset = elementSet.subSet("b", "f");
    ASSERT.that(subset).hasContentsInOrder("b", "c", "d", "e");

    assertTrue(subset.removeAll(Arrays.asList("a", "c")));
    ASSERT.that(elementSet).hasContentsInOrder("a", "b", "d", "e", "f");
    ASSERT.that(subset).hasContentsInOrder("b", "d", "e");
    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();
    ASSERT.that(elementSet).hasContentsInOrder("a", "b", "c", "d", "e", "f");
    SortedSet<String> subset = elementSet.subSet("b", "f");
    ASSERT.that(subset).hasContentsInOrder("b", "c", "d", "e");

    assertTrue(subset.retainAll(Arrays.asList("a", "c")));
    ASSERT.that(elementSet).hasContentsInOrder("a", "c", "f");
    ASSERT.that(subset).hasContentsInOrder("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();
    ASSERT.that(elementSet).hasContentsInOrder("a", "b", "c", "d", "e", "f");
    SortedSet<String> subset = elementSet.subSet("b", "f");
    ASSERT.that(subset).hasContentsInOrder("b", "c", "d", "e");

    subset.clear();
    ASSERT.that(elementSet).hasContentsInOrder("a", "f");
    ASSERT.that(subset).hasContentsInOrder();
    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");

    ASSERT.that(ms).hasContentsInOrder("d", "c", "b", "b", "a");

    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);

    ASSERT.that(ms).hasContentsInOrder(null, null, null, "a", "b", "b");
    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());
  }

  @Override public void testToStringNull() {
    c = ms = TreeMultiset.create(Ordering.natural().nullsFirst());
    super.testToStringNull();
  }
}