/*
 *  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.codehaus.groovy.util;

import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.NoSuchElementException;
import java.util.concurrent.ConcurrentLinkedQueue;

/**
 * A queue that stores values wrapped in a Reference, the type of which is
 * determined by the provided {@link ReferenceBundle}. References stored
 * in this queue will be removed when reference processing occurs.
 * <p>
 * This queue is backed by a {@link ConcurrentLinkedQueue} and is thread safe.
 * The iterator will only return non-null values (reachable) and is based on
 * the "weakly consistent" iterator of the underlying {@link ConcurrentLinkedQueue}.
 *
 * @param <T> the type of values to store
 */
public class ManagedConcurrentLinkedQueue<T> implements Iterable<T> {

    private final ReferenceBundle bundle;
    private final ConcurrentLinkedQueue<Element<T>> queue;

    /**
     * Creates an empty ManagedConcurrentLinkedQueue that will use the provided
     * {@code ReferenceBundle} to store values as the given Reference
     * type.
     *
     * @param bundle used to create the appropriate Reference type
     *               for the values stored
     */
    public ManagedConcurrentLinkedQueue(ReferenceBundle bundle) {
        this.bundle = bundle;
        this.queue = new ConcurrentLinkedQueue<Element<T>>();
    }

    /**
     * Adds the specified value to the queue.
     *
     * @param value the value to add
     */
    public void add(T value) {
        Element<T> e = new Element<T>(value);
        queue.offer(e);
    }

    /**
     * Returns {@code true} if this queue contains no elements.
     * <p>
     * This method does not check the elements to verify they contain
     * non-null reference values.
     */
    public boolean isEmpty() {
        return queue.isEmpty();
    }

    /**
     * Returns an array containing all values from this queue in the sequence they
     * were added.
     *
     * @param tArray the array to populate if big enough, else a new array with
     *               the same runtime type
     * @return an array containing all non-null values in this queue
     */
    public T[] toArray(T[] tArray) {
        return values().toArray(tArray);
    }

    /**
     * Returns a list containing all values from this queue in the
     * sequence they were added.
     */
    public List<T> values() {
        List<T> result = new ArrayList<T>();
        for (T t : this) {
            result.add(t);
        }
        return result;
    }

    /**
     * Returns an iterator over all non-null values in this queue.  The values should be
     * returned in the order they were added.
     */
    @Override
    public Iterator<T> iterator() {
        return new Itr(queue.iterator());
    }

    private class Element<V> extends ManagedReference<V> {

        Element(V value) {
            super(bundle, value);
        }

        @Override
        public void finalizeReference() {
            queue.remove(this);
            super.finalizeReference();
        }

    }

    private class Itr implements Iterator<T> {

        final Iterator<Element<T>> wrapped;

        T value;
        Element<T> current;
        boolean exhausted;

        Itr(Iterator<Element<T>> wrapped) {
            this.wrapped = wrapped;
        }

        @Override
        public boolean hasNext() {
            if (!exhausted && value == null) {
                advance();
            }
            return value != null;
        }

        @Override
        public T next() {
            if (!hasNext()) {
                throw new NoSuchElementException();
            }
            T next = value;
            value = null;
            return next;
        }

        @Override
        public void remove() {
            if (current == null || value != null) {
                throw new IllegalStateException("Next method has not been called");
            }
            wrapped.remove();
            current = null;
        }

        private void advance() {
            while (wrapped.hasNext()) {
                Element<T> e = wrapped.next();
                T v = e.get();
                if (v != null) {
                    current = e;
                    value = v;
                    return;
                }
                wrapped.remove();
            }
            value = null;
            current = null;
            exhausted = true;
        }

    }

}
