/**
 * OW2 Specifications
 * Copyright (C) 2011 Bull S.A.S.
 * Contact: easybeans@ow2.org
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 2.1 of the License, or (at your option) any later version.
 *
 * This library is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA
 *
 * --------------------------------------------------------------------------
 * $Id: ForwardingRuntimeDelegate.java 5871 2011-05-05 12:11:35Z benoitf $
 * --------------------------------------------------------------------------
 */

package org.ow2.spec.ee.jaxrs;

import java.util.HashMap;
import java.util.Map;

import javax.ws.rs.core.Application;
import javax.ws.rs.core.Response;
import javax.ws.rs.core.UriBuilder;
import javax.ws.rs.core.Variant;
import javax.ws.rs.ext.RuntimeDelegate;

import org.osgi.framework.BundleContext;
import org.osgi.framework.ServiceEvent;
import org.osgi.framework.ServiceListener;
import org.osgi.framework.ServiceReference;

/**
 * A {@code ForwardingRuntimeDelegate} is a JAX-RS RuntimeDelegate that
 * handles the dynamism of RuntimeDelegate OSGi services.
 *
 * @author Guillaume Sauthier
 */
public class ForwardingRuntimeDelegate extends RuntimeDelegate implements ServiceListener {

    /**
     * BundleContext
     */
    private BundleContext bundleContext;

    /**
     * All currently available delegates.
     */
    private Map<ServiceReference, RuntimeDelegate> delegates;

    /**
     * Delegate in use (may be null if no delegate is available)
     */
    private RuntimeDelegate preferred;

    public ForwardingRuntimeDelegate(final BundleContext bundleContext) {
        this.bundleContext = bundleContext;
        this.delegates = new HashMap<ServiceReference, RuntimeDelegate>();
    }

    @Override
    public UriBuilder createUriBuilder() {
        checkDelegateIsAvailable();
        return preferred.createUriBuilder();
    }

    @Override
    public Response.ResponseBuilder createResponseBuilder() {
        checkDelegateIsAvailable();
        return preferred.createResponseBuilder();
    }

    @Override
    public Variant.VariantListBuilder createVariantListBuilder() {
        checkDelegateIsAvailable();
        return preferred.createVariantListBuilder();
    }

    @Override
    public <T> T createEndpoint(final Application application,
                                final Class<T> endpointType)
            throws IllegalArgumentException, UnsupportedOperationException {

        checkDelegateIsAvailable();
        return preferred.createEndpoint(application, endpointType);
    }

    @Override
    public <T> HeaderDelegate<T> createHeaderDelegate(final Class<T> type) {
        checkDelegateIsAvailable();
        return preferred.createHeaderDelegate(type);
    }

    public void serviceChanged(final ServiceEvent event) {
        switch (event.getType()) {
            case ServiceEvent.MODIFIED:
                onModification(event.getServiceReference());
                break;
            case ServiceEvent.REGISTERED:
                onRegistration(event.getServiceReference());
                break;
            case ServiceEvent.UNREGISTERING:
                onUnregistration(event.getServiceReference());
                break;
        }
    }

    public void onModification(final ServiceReference reference) {
        // Only properties have changed, nothing to do
    }

    public void onRegistration(final ServiceReference reference) {
        RuntimeDelegate delegate = (RuntimeDelegate) bundleContext.getService(reference);
        delegates.put(reference, delegate);
        if (preferred == null) {
            preferred = delegate;
        }
    }

    public void onUnregistration(final ServiceReference reference) {
        RuntimeDelegate delegate = delegates.remove(reference);
        // Test if this is the same object
        if (preferred == delegate) {
            preferred = getFirstAvailable();
        }
    }

    private RuntimeDelegate getFirstAvailable() {
        RuntimeDelegate delegate = null;
        if (!delegates.isEmpty()) {
            // Take the first delegate
            delegate = delegates.values().iterator().next();
        }
        // We may return null, that's OK
        return delegate;
    }

    /**
     * Starts services dependencies management of this instance.
     */
    public void start() throws Exception {
        // Browse directly available services
        ServiceReference[] references = this.bundleContext.getServiceReferences(RuntimeDelegate.class.getName(), null);
        if (references != null) {
            for (ServiceReference reference : references) {
                onRegistration(reference);
            }
        }
        // Register a listener
        this.bundleContext.addServiceListener(this, "(objectClass=" + RuntimeDelegate.class.getName() + ")");
    }

    public void stop() {
        this.bundleContext.removeServiceListener(this);
        this.delegates.clear();
    }

    private void checkDelegateIsAvailable() {
        if (preferred == null) {
            throw new IllegalStateException("No " + RuntimeDelegate.class.getName() + " service is available.");
        }
    }

}
