/*
 * 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.Multisets.setCountImpl;

import java.util.AbstractCollection;
import java.util.Collection;
import java.util.Iterator;
import java.util.Set;

import javax.annotation.Nullable;

import com.google.common.annotations.GwtCompatible;
import com.google.common.base.Objects;

/**
 * This class provides a skeletal implementation of the {@link Multiset}
 * interface. A new multiset implementation can be created easily by extending
 * this class and implementing the {@link Multiset#entrySet()} method, plus
 * optionally overriding {@link #add(Object, int)} and
 * {@link #remove(Object, int)} to enable modifications to the multiset.
 *
 * <p>
 * The {@link #count} and {@link #size} implementations all iterate across the
 * set returned by {@link Multiset#entrySet()}, as do many methods acting on the
 * set returned by {@link #elementSet()}. Override those methods for better
 * performance.
 *
 * @author Kevin Bourrillion
 * @author Louis Wasserman
 */
@GwtCompatible
abstract class AbstractMultiset<E> extends AbstractCollection<E> implements Multiset<E> {
	// Query Operations

	@Override
	public int size() {
		return Multisets.sizeImpl(this);
	}

	@Override
	public boolean isEmpty() {
		return entrySet().isEmpty();
	}

	@Override
	public boolean contains(@Nullable Object element) {
		return count(element) > 0;
	}

	@Override
	public Iterator<E> iterator() {
		return Multisets.iteratorImpl(this);
	}

	@Override
	public int count(@Nullable Object element) {
		for (Entry<E> entry : entrySet()) {
			if (Objects.equal(entry.getElement(), element)) {
				return entry.getCount();
			}
		}
		return 0;
	}

	// Modification Operations

	@Override
	public boolean add(@Nullable E element) {
		add(element, 1);
		return true;
	}

	@Override
	public int add(@Nullable E element, int occurrences) {
		throw new UnsupportedOperationException();
	}

	@Override
	public boolean remove(@Nullable Object element) {
		return remove(element, 1) > 0;
	}

	@Override
	public int remove(@Nullable Object element, int occurrences) {
		throw new UnsupportedOperationException();
	}

	@Override
	public int setCount(@Nullable E element, int count) {
		return setCountImpl(this, element, count);
	}

	@Override
	public boolean setCount(@Nullable E element, int oldCount, int newCount) {
		return setCountImpl(this, element, oldCount, newCount);
	}

	// Bulk Operations

	/**
	 * {@inheritDoc}
	 *
	 * <p>
	 * This implementation is highly efficient when {@code elementsToAdd} is itself
	 * a {@link Multiset}.
	 */
	@Override
	public boolean addAll(Collection<? extends E> elementsToAdd) {
		return Multisets.addAllImpl(this, elementsToAdd);
	}

	@Override
	public boolean removeAll(Collection<?> elementsToRemove) {
		return Multisets.removeAllImpl(this, elementsToRemove);
	}

	@Override
	public boolean retainAll(Collection<?> elementsToRetain) {
		return Multisets.retainAllImpl(this, elementsToRetain);
	}

	@Override
	public void clear() {
		Iterators.clear(entryIterator());
	}

	// Views

	private transient Set<E> elementSet;

	@Override
	public Set<E> elementSet() {
		Set<E> result = elementSet;
		if (result == null) {
			elementSet = result = createElementSet();
		}
		return result;
	}

	/**
	 * Creates a new instance of this multiset's element set, which will be returned
	 * by {@link #elementSet()}.
	 */
	Set<E> createElementSet() {
		return new ElementSet();
	}

	class ElementSet extends Multisets.ElementSet<E> {
		@Override
		Multiset<E> multiset() {
			return AbstractMultiset.this;
		}
	}

	abstract Iterator<Entry<E>> entryIterator();

	abstract int distinctElements();

	private transient Set<Entry<E>> entrySet;

	@Override
	public Set<Entry<E>> entrySet() {
		Set<Entry<E>> result = entrySet;
		return (result == null) ? entrySet = createEntrySet() : result;
	}

	class EntrySet extends Multisets.EntrySet<E> {
		@Override
		Multiset<E> multiset() {
			return AbstractMultiset.this;
		}

		@Override
		public Iterator<Entry<E>> iterator() {
			return entryIterator();
		}

		@Override
		public int size() {
			return distinctElements();
		}
	}

	Set<Entry<E>> createEntrySet() {
		return new EntrySet();
	}

	// Object methods

	/**
	 * {@inheritDoc}
	 *
	 * <p>
	 * This implementation returns {@code true} if {@code object} is a multiset of
	 * the same size and if, for each element, the two multisets have the same
	 * count.
	 */
	@Override
	public boolean equals(@Nullable Object object) {
		return Multisets.equalsImpl(this, object);
	}

	/**
	 * {@inheritDoc}
	 *
	 * <p>
	 * This implementation returns the hash code of {@link Multiset#entrySet()}.
	 */
	@Override
	public int hashCode() {
		return entrySet().hashCode();
	}

	/**
	 * {@inheritDoc}
	 *
	 * <p>
	 * This implementation returns the result of invoking {@code toString} on
	 * {@link Multiset#entrySet()}.
	 */
	@Override
	public String toString() {
		return entrySet().toString();
	}
}
