/*
 * Decompiled with CFR 0.152.
 */
package com.google.common.collect;

import com.google.common.annotations.Beta;
import com.google.common.annotations.VisibleForTesting;
import com.google.common.base.Preconditions;
import com.google.common.collect.AbstractIterator;
import com.google.common.collect.AbstractMultiset;
import com.google.common.collect.CollectPreconditions;
import com.google.common.collect.Collections2;
import com.google.common.collect.ForwardingIterator;
import com.google.common.collect.ForwardingSet;
import com.google.common.collect.Iterables;
import com.google.common.collect.Iterators;
import com.google.common.collect.Lists;
import com.google.common.collect.MapMaker;
import com.google.common.collect.Maps;
import com.google.common.collect.Multiset;
import com.google.common.collect.Multisets;
import com.google.common.collect.Serialization;
import com.google.common.math.IntMath;
import com.google.common.primitives.Ints;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
import java.util.concurrent.atomic.AtomicInteger;
import javax.annotation.Nullable;

public final class ConcurrentHashMultiset<E>
extends AbstractMultiset<E>
implements Serializable {
    private final transient ConcurrentMap<E, AtomicInteger> countMap;
    private static final long serialVersionUID = 1L;

    public static <E> ConcurrentHashMultiset<E> create() {
        return new ConcurrentHashMultiset(new ConcurrentHashMap());
    }

    public static <E> ConcurrentHashMultiset<E> create(Iterable<? extends E> elements) {
        ConcurrentHashMultiset<E> multiset = ConcurrentHashMultiset.create();
        Iterables.addAll(multiset, elements);
        return multiset;
    }

    @Beta
    public static <E> ConcurrentHashMultiset<E> create(MapMaker mapMaker) {
        return new ConcurrentHashMultiset(mapMaker.makeMap());
    }

    @VisibleForTesting
    ConcurrentHashMultiset(ConcurrentMap<E, AtomicInteger> countMap) {
        Preconditions.checkArgument(countMap.isEmpty());
        this.countMap = countMap;
    }

    @Override
    public int count(@Nullable Object element) {
        AtomicInteger existingCounter = Maps.safeGet(this.countMap, element);
        return existingCounter == null ? 0 : existingCounter.get();
    }

    @Override
    public int size() {
        long sum = 0L;
        for (AtomicInteger value : this.countMap.values()) {
            sum += (long)value.get();
        }
        return Ints.saturatedCast(sum);
    }

    @Override
    public Object[] toArray() {
        return this.snapshot().toArray();
    }

    @Override
    public <T> T[] toArray(T[] array) {
        return this.snapshot().toArray(array);
    }

    private List<E> snapshot() {
        ArrayList list = Lists.newArrayListWithExpectedSize(this.size());
        for (Multiset.Entry entry : this.entrySet()) {
            Object element = entry.getElement();
            for (int i = entry.getCount(); i > 0; --i) {
                list.add(element);
            }
        }
        return list;
    }

    @Override
    public int add(E element, int occurrences) {
        AtomicInteger existingCounter;
        AtomicInteger newCounter;
        Preconditions.checkNotNull(element);
        if (occurrences == 0) {
            return this.count(element);
        }
        Preconditions.checkArgument(occurrences > 0, "Invalid occurrences: %s", occurrences);
        do {
            int oldValue;
            if ((existingCounter = Maps.safeGet(this.countMap, element)) == null && (existingCounter = this.countMap.putIfAbsent(element, new AtomicInteger(occurrences))) == null) {
                return 0;
            }
            while ((oldValue = existingCounter.get()) != 0) {
                try {
                    int newValue = IntMath.checkedAdd(oldValue, occurrences);
                    if (!existingCounter.compareAndSet(oldValue, newValue)) continue;
                    return oldValue;
                }
                catch (ArithmeticException overflow) {
                    int n = occurrences;
                    int n2 = oldValue;
                    throw new IllegalArgumentException(new StringBuilder(65).append("Overflow adding ").append(n).append(" occurrences to a count of ").append(n2).toString());
                }
            }
        } while (this.countMap.putIfAbsent(element, newCounter = new AtomicInteger(occurrences)) != null && !this.countMap.replace(element, existingCounter, newCounter));
        return 0;
    }

    @Override
    public int remove(@Nullable Object element, int occurrences) {
        int oldValue;
        if (occurrences == 0) {
            return this.count(element);
        }
        Preconditions.checkArgument(occurrences > 0, "Invalid occurrences: %s", occurrences);
        AtomicInteger existingCounter = Maps.safeGet(this.countMap, element);
        if (existingCounter == null) {
            return 0;
        }
        while ((oldValue = existingCounter.get()) != 0) {
            int newValue = Math.max(0, oldValue - occurrences);
            if (!existingCounter.compareAndSet(oldValue, newValue)) continue;
            if (newValue == 0) {
                this.countMap.remove(element, existingCounter);
            }
            return oldValue;
        }
        return 0;
    }

    public boolean removeExactly(@Nullable Object element, int occurrences) {
        int newValue;
        int oldValue;
        if (occurrences == 0) {
            return true;
        }
        Preconditions.checkArgument(occurrences > 0, "Invalid occurrences: %s", occurrences);
        AtomicInteger existingCounter = Maps.safeGet(this.countMap, element);
        if (existingCounter == null) {
            return false;
        }
        do {
            if ((oldValue = existingCounter.get()) >= occurrences) continue;
            return false;
        } while (!existingCounter.compareAndSet(oldValue, newValue = oldValue - occurrences));
        if (newValue == 0) {
            this.countMap.remove(element, existingCounter);
        }
        return true;
    }

    @Override
    public int setCount(E element, int count) {
        int oldValue;
        AtomicInteger existingCounter;
        Preconditions.checkNotNull(element);
        CollectPreconditions.checkNonnegative(count, "count");
        block0: while (true) {
            if ((existingCounter = Maps.safeGet(this.countMap, element)) == null) {
                if (count == 0) {
                    return 0;
                }
                existingCounter = this.countMap.putIfAbsent(element, new AtomicInteger(count));
                if (existingCounter == null) {
                    return 0;
                }
            }
            do {
                if ((oldValue = existingCounter.get()) != 0) continue;
                if (count == 0) {
                    return 0;
                }
                AtomicInteger newCounter = new AtomicInteger(count);
                if (this.countMap.putIfAbsent(element, newCounter) != null && !this.countMap.replace(element, existingCounter, newCounter)) continue block0;
                return 0;
            } while (!existingCounter.compareAndSet(oldValue, count));
            break;
        }
        if (count == 0) {
            this.countMap.remove(element, existingCounter);
        }
        return oldValue;
    }

    @Override
    public boolean setCount(E element, int expectedOldCount, int newCount) {
        Preconditions.checkNotNull(element);
        CollectPreconditions.checkNonnegative(expectedOldCount, "oldCount");
        CollectPreconditions.checkNonnegative(newCount, "newCount");
        AtomicInteger existingCounter = Maps.safeGet(this.countMap, element);
        if (existingCounter == null) {
            if (expectedOldCount != 0) {
                return false;
            }
            if (newCount == 0) {
                return true;
            }
            return this.countMap.putIfAbsent(element, new AtomicInteger(newCount)) == null;
        }
        int oldValue = existingCounter.get();
        if (oldValue == expectedOldCount) {
            if (oldValue == 0) {
                if (newCount == 0) {
                    this.countMap.remove(element, existingCounter);
                    return true;
                }
                AtomicInteger newCounter = new AtomicInteger(newCount);
                return this.countMap.putIfAbsent(element, newCounter) == null || this.countMap.replace(element, existingCounter, newCounter);
            }
            if (existingCounter.compareAndSet(oldValue, newCount)) {
                if (newCount == 0) {
                    this.countMap.remove(element, existingCounter);
                }
                return true;
            }
        }
        return false;
    }

    @Override
    Set<E> createElementSet() {
        final Set delegate = this.countMap.keySet();
        return new ForwardingSet<E>(){

            @Override
            protected Set<E> delegate() {
                return delegate;
            }

            @Override
            public boolean contains(@Nullable Object object) {
                return object != null && Collections2.safeContains(delegate, object);
            }

            @Override
            public boolean containsAll(Collection<?> collection) {
                return this.standardContainsAll(collection);
            }

            @Override
            public boolean remove(Object object) {
                return object != null && Collections2.safeRemove(delegate, object);
            }

            @Override
            public boolean removeAll(Collection<?> c) {
                return this.standardRemoveAll(c);
            }
        };
    }

    @Override
    public Set<Multiset.Entry<E>> createEntrySet() {
        return new EntrySet();
    }

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

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

    @Override
    Iterator<Multiset.Entry<E>> entryIterator() {
        final AbstractIterator readOnlyIterator = new AbstractIterator<Multiset.Entry<E>>(){
            private Iterator<Map.Entry<E, AtomicInteger>> mapEntries;
            {
                this.mapEntries = ConcurrentHashMultiset.this.countMap.entrySet().iterator();
            }

            @Override
            protected Multiset.Entry<E> computeNext() {
                Map.Entry mapEntry;
                int count;
                do {
                    if (this.mapEntries.hasNext()) continue;
                    return (Multiset.Entry)this.endOfData();
                } while ((count = (mapEntry = this.mapEntries.next()).getValue().get()) == 0);
                return Multisets.immutableEntry(mapEntry.getKey(), count);
            }
        };
        return new ForwardingIterator<Multiset.Entry<E>>(){
            private Multiset.Entry<E> last;

            @Override
            protected Iterator<Multiset.Entry<E>> delegate() {
                return readOnlyIterator;
            }

            @Override
            public Multiset.Entry<E> next() {
                this.last = (Multiset.Entry)super.next();
                return this.last;
            }

            @Override
            public void remove() {
                CollectPreconditions.checkRemove(this.last != null);
                ConcurrentHashMultiset.this.setCount(this.last.getElement(), 0);
                this.last = null;
            }
        };
    }

    @Override
    public void clear() {
        this.countMap.clear();
    }

    private void writeObject(ObjectOutputStream stream) throws IOException {
        stream.defaultWriteObject();
        stream.writeObject(this.countMap);
    }

    private void readObject(ObjectInputStream stream) throws IOException, ClassNotFoundException {
        stream.defaultReadObject();
        ConcurrentMap deserializedCountMap = (ConcurrentMap)stream.readObject();
        FieldSettersHolder.COUNT_MAP_FIELD_SETTER.set(this, deserializedCountMap);
    }

    private class EntrySet
    extends AbstractMultiset.EntrySet {
        private EntrySet() {
            super(ConcurrentHashMultiset.this);
        }

        @Override
        ConcurrentHashMultiset<E> multiset() {
            return ConcurrentHashMultiset.this;
        }

        @Override
        public Object[] toArray() {
            return this.snapshot().toArray();
        }

        @Override
        public <T> T[] toArray(T[] array) {
            return this.snapshot().toArray(array);
        }

        private List<Multiset.Entry<E>> snapshot() {
            ArrayList list = Lists.newArrayListWithExpectedSize(this.size());
            Iterators.addAll(list, this.iterator());
            return list;
        }
    }

    private static class FieldSettersHolder {
        static final Serialization.FieldSetter<ConcurrentHashMultiset> COUNT_MAP_FIELD_SETTER = Serialization.getFieldSetter(ConcurrentHashMultiset.class, "countMap");

        private FieldSettersHolder() {
        }
    }
}
