/*
 * 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.base.Preconditions.checkArgument;

import com.google.common.annotations.GwtCompatible;
import com.google.common.collect.Multiset.Entry;

import java.io.Serializable;
import java.util.Iterator;
import java.util.Map;
import java.util.concurrent.atomic.AtomicInteger;

/**
 * Unit test for {@link AbstractMultiset}.
 *
 * @author Kevin Bourrillion
 * @author Louis Wasserman
 */
@SuppressWarnings("serial") // No serialization is used in this test
@GwtCompatible
public class SimpleAbstractMultisetTest extends AbstractMultisetTest {

  @Override protected <E> Multiset<E> create() {
    return new SimpleAbstractMultiset<E>();
  }

  public void testFastAddAllMultiset() {
    final AtomicInteger addCalls = new AtomicInteger();
    Multiset<String> multiset = new NoRemoveMultiset<String>() {
      @Override
      public int add(String element, int occurrences) {
        addCalls.incrementAndGet();
        return super.add(element, occurrences);
      }
    };
    ImmutableMultiset<String> adds =
        new ImmutableMultiset.Builder<String>().addCopies("x", 10).build();
    multiset.addAll(adds);
    assertEquals(addCalls.get(), 1);
  }

  public void testRemoveUnsupported() {
    Multiset<String> multiset = new NoRemoveMultiset<String>();
    multiset.add("a");
    try {
      multiset.remove("a");
      fail();
    } catch (UnsupportedOperationException expected) {}
    assertTrue(multiset.contains("a"));
  }

  private static class NoRemoveMultiset<E> extends AbstractMultiset<E>
      implements Serializable {
    final Map<E, Integer> backingMap = Maps.newHashMap();

    @Override public int add(E element, int occurrences) {
      checkArgument(occurrences >= 0);
      Integer frequency = backingMap.get(element);
      if (frequency == null) {
        frequency = 0;
      }
      if (occurrences == 0) {
        return frequency;
      }
      checkArgument(occurrences <= Integer.MAX_VALUE - frequency);
      backingMap.put(element, frequency + occurrences);
      return frequency;
    }

    @Override
    Iterator<Entry<E>> entryIterator() {
      final Iterator<Map.Entry<E, Integer>> backingEntries = backingMap.entrySet().iterator();
      return new Iterator<Multiset.Entry<E>>() {
        @Override
        public boolean hasNext() {
          return backingEntries.hasNext();
        }

        @Override
        public Multiset.Entry<E> next() {
          final Map.Entry<E, Integer> mapEntry = backingEntries.next();
          return new Multisets.AbstractEntry<E>() {
            @Override
            public E getElement() {
              return mapEntry.getKey();
            }

            @Override
            public int getCount() {
              Integer frequency = backingMap.get(getElement());
              return (frequency == null) ? 0 : frequency;
            }
          };
        }

        @Override
        public void remove() {
          backingEntries.remove();
        }
      };
    }

    @Override
    int distinctElements() {
      return backingMap.size();
    }
  }

  private static class SimpleAbstractMultiset<E> extends NoRemoveMultiset<E> {
    @SuppressWarnings("unchecked")
    @Override public int remove(Object element, int occurrences) {
      checkArgument(occurrences >= 0);
      Integer count = backingMap.get(element);
      if (count == null) {
        return 0;
      } else if (count > occurrences) {
        backingMap.put((E) element, count - occurrences);
        return count;
      } else {
        return backingMap.remove(element);
      }
    }
  }
}