/*******************************************************************************
 * Copyright (c) 2010, 2015 Sonatype, Inc.
 * All rights reserved. This program and the accompanying materials
 * are made available under the terms of the Eclipse Public License v1.0
 * which accompanies this distribution, and is available at
 * http://www.eclipse.org/legal/epl-v10.html
 *
 * Contributors:
 *    Stuart McCulloch (Sonatype, Inc.) - initial API and implementation
 *******************************************************************************/
package org.eclipse.sisu.inject;

import java.lang.annotation.Annotation;
import java.util.Collection;
import java.util.Iterator;

import com.google.inject.Binding;
import com.google.inject.TypeLiteral;

/**
 * Ordered sequence of {@link Binding}s of a given type; subscribes to {@link BindingPublisher}s on demand.
 */
final class RankedBindings<T>
    implements Iterable<Binding<T>>, BindingSubscriber<T>
{
    // ----------------------------------------------------------------------
    // Implementation fields
    // ----------------------------------------------------------------------

    final transient RankedSequence<Binding<T>> bindings = new RankedSequence<Binding<T>>();

    final transient TypeLiteral<T> type;

    final transient RankedSequence<BindingPublisher> pendingPublishers;

    final Collection<BeanCache<?, T>> cachedBeans = Weak.elements();

    // ----------------------------------------------------------------------
    // Constructors
    // ----------------------------------------------------------------------

    RankedBindings( final TypeLiteral<T> type, final RankedSequence<BindingPublisher> publishers )
    {
        this.type = type;
        this.pendingPublishers = new RankedSequence<BindingPublisher>( publishers );
    }

    // ----------------------------------------------------------------------
    // Public methods
    // ----------------------------------------------------------------------

    public TypeLiteral<T> type()
    {
        return type;
    }

    public void add( final Binding<T> binding, final int rank )
    {
        bindings.insert( binding, rank );
    }

    public void remove( final Binding<T> binding )
    {
        if ( bindings.removeThis( binding ) )
        {
            synchronized ( cachedBeans )
            {
                for ( final BeanCache<?, T> beans : cachedBeans )
                {
                    beans.remove( binding );
                }
            }
        }
    }

    public Iterable<Binding<T>> bindings()
    {
        return bindings.snapshot();
    }

    public Itr iterator()
    {
        return new Itr();
    }

    // ----------------------------------------------------------------------
    // Local methods
    // ----------------------------------------------------------------------

    <Q extends Annotation> BeanCache<Q, T> newBeanCache()
    {
        final BeanCache<Q, T> beans = new BeanCache<Q, T>();
        synchronized ( cachedBeans )
        {
            cachedBeans.add( beans );
        }
        return beans;
    }

    void add( final BindingPublisher publisher, final int rank )
    {
        /*
         * No need to lock; ranked sequence is thread-safe.
         */
        pendingPublishers.insert( publisher, rank );
    }

    void remove( final BindingPublisher publisher )
    {
        /*
         * Lock just to prevent subscription race condition.
         */
        synchronized ( publisher )
        {
            if ( !pendingPublishers.removeThis( publisher ) )
            {
                publisher.unsubscribe( this );
            }
        }
    }

    // ----------------------------------------------------------------------
    // Implementation types
    // ----------------------------------------------------------------------

    /**
     * {@link Binding} iterator that only subscribes to {@link BindingPublisher}s as required.
     */
    final class Itr
        implements Iterator<Binding<T>>
    {
        // ----------------------------------------------------------------------
        // Implementation fields
        // ----------------------------------------------------------------------

        private final RankedSequence<Binding<T>>.Itr itr = bindings.iterator();

        // ----------------------------------------------------------------------
        // Public methods
        // ----------------------------------------------------------------------

        public boolean hasNext()
        {
            // apply any publishers that could add bindings before the current position
            BindingPublisher publisher = pendingPublishers.peek();
            while ( null != publisher && !itr.hasNext( publisher.maxBindingRank() ) )
            {
                synchronized ( publisher )
                {
                    // check in case subscribed by another thread
                    if ( publisher == pendingPublishers.peek() )
                    {
                        // only update list _after_ subscription
                        publisher.subscribe( RankedBindings.this );
                        pendingPublishers.removeThis( publisher );
                    }
                }
                publisher = pendingPublishers.peek();
            }
            return itr.hasNext();
        }

        public Binding<T> next()
        {
            return itr.next();
        }

        public int rank()
        {
            return itr.rank();
        }

        public void remove()
        {
            throw new UnsupportedOperationException();
        }
    }
}
