/*
 * Licensed to the Apache Software Foundation (ASF) under one or more
 * contributor license agreements.  See the NOTICE file distributed with
 * this work for additional information regarding copyright ownership.
 * The ASF licenses this file to You 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 org.apache.ignite.internal.util;

import java.util.Collection;
import java.util.Comparator;
import java.util.NoSuchElementException;
import java.util.SortedSet;
import java.util.concurrent.atomic.AtomicInteger;

/**
 * Concurrent ordered set that automatically manages its maximum size.
 * Once it exceeds its maximum, it will start removing smallest elements
 * until the maximum is reached again.
 * <p>
 * Note that due to concurrent nature of this set, it may grow slightly
 * larger than its maximum allowed size, but in this case it will quickly
 * readjust back to allowed size.
 * <p>
 * Note that {@link #remove(Object)} method is not supported for this kind of set.
 */
public class GridBoundedConcurrentOrderedSet<E> extends GridConcurrentSkipListSet<E> {
    /** */
    private static final long serialVersionUID = 0L;

    /** Element count. */
    private final AtomicInteger cnt = new AtomicInteger(0);

    /** Maximum size. */
    private int max;

    /**
     * Constructs a new, empty set that orders its elements according to
     * their {@linkplain Comparable natural ordering}.
     *
     * @param max Upper bound of this set.
     */
    public GridBoundedConcurrentOrderedSet(int max) {
        assert max > 0;

        this.max = max;
    }

    /**
     * Constructs a new, empty set that orders its elements according to
     * the specified comparator.
     *
     * @param max Upper bound of this set.
     * @param comp the comparator that will be used to order this set.
     *      If <tt>null</tt>, the {@linkplain Comparable natural
     *      ordering} of the elements will be used.
     */
    public GridBoundedConcurrentOrderedSet(int max, Comparator<? super E> comp) {
        super(comp);

        assert max > 0;

        this.max = max;
    }

    /**
     * Constructs a new set containing the elements in the specified
     * collection, that orders its elements according to their
     * {@linkplain Comparable natural ordering}.
     *
     * @param max Upper bound of this set.
     * @param c The elements that will comprise the new set
     * @throws ClassCastException if the elements in <tt>c</tt> are
     *      not {@link Comparable}, or are not mutually comparable
     * @throws NullPointerException if the specified collection or any
     *      of its elements are {@code null}.
     */
    public GridBoundedConcurrentOrderedSet(int max, Collection<? extends E> c) {
        super(c);

        assert max > 0;

        this.max = max;
    }

    /**
     * Constructs a new set containing the same elements and using the
     * same ordering as the specified sorted set.
     *
     * @param max Upper bound of this set.
     * @param s sorted set whose elements will comprise the new set
     * @throws NullPointerException if the specified sorted set or any
     *      of its elements are {@code null}.
     */
    public GridBoundedConcurrentOrderedSet(int max, SortedSet<E> s) {
        super(s);

        assert max > 0;

        this.max = max;
    }

    /** {@inheritDoc} */
    @Override public boolean add(E e) {
        GridArgumentCheck.notNull(e, "e");

        if (super.add(e)) {
            cnt.incrementAndGet();

            int c;

            while ((c = cnt.get()) > max) {
                // Decrement count.
                if (cnt.compareAndSet(c, c - 1)) {
                    try {
                        while (!super.remove(first())) {
                            // No-op.
                        }
                    }
                    catch (NoSuchElementException e1) {
                        e1.printStackTrace(); // Should never happen.

                        assert false : "Internal error in grid bounded ordered set.";
                    }
                }
            }

            return true;
        }

        return false;
    }

    /**
     * Approximate size at this point of time. Note, that unlike {@code size}
     * methods on other {@code concurrent} collections, this method executes
     * in constant time without traversal of the elements.
     *
     * @return Approximate set size at this point of time.
     */
    @Override public int size() {
        int size = cnt.get();

        return size < 0 ? 0 : size;
    }

    /** {@inheritDoc} */
    @Override public GridBoundedConcurrentOrderedSet<E> clone() {
        GridBoundedConcurrentOrderedSet<E> s = (GridBoundedConcurrentOrderedSet<E>)super.clone();

        s.max = max;

        return s;
    }

    /**
     * This method is not supported and always throws {@link UnsupportedOperationException}.
     *
     * @param o {@inheritDoc}
     * @return {@inheritDoc}
     */
    @Override public boolean remove(Object o) {
        if (super.remove(o)) {
            cnt.decrementAndGet();

            return true;
        }

        return false;
    }
}
