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

import java.util.Collection;

import org.eclipse.sisu.inject.BindingSubscriber;
import org.eclipse.sisu.inject.Logs;
import org.eclipse.sisu.inject.Weak;
import org.osgi.framework.BundleContext;
import org.osgi.framework.ServiceReference;
import org.osgi.util.tracker.ServiceTracker;

/**
 * Tracker of {@link ServiceBinding}s from the OSGi service registry.
 */
final class BindingTracker<T>
    extends ServiceTracker<T, ServiceBinding<T>>
{
    // ----------------------------------------------------------------------
    // Implementation fields
    // ----------------------------------------------------------------------

    private final Collection<BindingSubscriber<T>> subscribers = Weak.elements();

    private final String clazzName;

    private final int maxRank;

    private boolean isOpen;

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

    BindingTracker( final BundleContext context, final int maxRank, final String clazzName )
    {
        super( context, clazzName, null );
        this.clazzName = clazzName;
        this.maxRank = maxRank;
    }

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

    public void subscribe( final BindingSubscriber<T> subscriber )
    {
        synchronized ( subscribers )
        {
            openIfNecessary();
            for ( final ServiceBinding<T> binding : getTracked().values() )
            {
                if ( binding.isCompatibleWith( subscriber ) )
                {
                    subscriber.add( binding, binding.rank() );
                }
            }
            subscribers.add( subscriber );
        }
    }

    public void unsubscribe( final BindingSubscriber<T> subscriber )
    {
        synchronized ( subscribers )
        {
            if ( subscribers.remove( subscriber ) )
            {
                for ( final ServiceBinding<T> binding : getTracked().values() )
                {
                    subscriber.remove( binding );
                }
            }
            closeIfNecessary();
        }
    }

    @Override
    public ServiceBinding<T> addingService( final ServiceReference<T> reference )
    {
        final ServiceBinding<T> binding;
        try
        {
            binding = new ServiceBinding<T>( context, clazzName, maxRank, reference );
        }
        catch ( final Exception e )
        {
            Logs.warn( "Problem subscribing to service: {}", reference, e );
            return null;
        }
        synchronized ( subscribers )
        {
            for ( final BindingSubscriber<T> subscriber : subscribers )
            {
                if ( binding.isCompatibleWith( subscriber ) )
                {
                    subscriber.add( binding, binding.rank() );
                }
            }
            closeIfNecessary();
        }
        return binding;
    }

    @Override
    public void removedService( final ServiceReference<T> reference, final ServiceBinding<T> binding )
    {
        synchronized ( subscribers )
        {
            for ( final BindingSubscriber<T> subscriber : subscribers )
            {
                subscriber.remove( binding );
            }
            closeIfNecessary();
        }
        super.removedService( reference, binding );
    }

    // ----------------------------------------------------------------------
    // Implementation methods
    // ----------------------------------------------------------------------

    private void openIfNecessary()
    {
        if ( !isOpen )
        {
            open( true ); // calls addingService to pre-fill the tracker
            Logs.trace( "Started tracking services: {}", filter, null );
            isOpen = true; // set last to avoid premature close
        }
    }

    private void closeIfNecessary()
    {
        if ( isOpen && subscribers.isEmpty() )
        {
            isOpen = false; // set first to avoid repeated close
            Logs.trace( "Stopped tracking services: {}", filter, null );
            close(); // calls removedService to clear out the tracker
        }
    }
}
