package de.renew.plugin;

import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
import java.util.Collection;


/**
 * Instances of this class represent a soft dependency between plugins.
 * A {@link SoftDependencyListener} implementation has to be provided by
 * users of this class. The listener is informed about the service
 * availability without redundancies. The listener is also informed if the
 * requested service is available from the very beginning of this dependency's
 * life span.
 * <p>
 * The <code>SoftDependencyListener</code> implementation class has to be
 * given as textual class name because it might depend on classes that are
 * not available when the requested service is not provided. The listener
 * object is instantiated the first time the service becomes available.
 * Due to this decoupling, complaints about listener instantiation cannot
 * go back to the caller, they are reported to the system output only.
 * </p>
 * <p>
 * <b>WARNING:</b>
 * The current implementation of this class assumes that a service is not
 * provided by more than one plugin at the same time. It may report
 * service unavailability although there is another service provider
 * available!
 * </p>
 *
 * @author Michael Duvigneau
 **/
public class SoftDependency implements IPluginManagerListener {
    /**
     * Logger for logging purposes as specified by Apache Log4j
     */
    public static final org.apache.log4j.Logger LOGGER =
        org.apache.log4j.Logger.getLogger(SoftDependency.class);

    /**
     * The service name to listen for.
     **/
    private String _pluginService;

    /**
     * The plugin that provides the service required by this soft
     * dependency.  If <code>null</code>, the service has not been
     * announced as available to the listener.
     **/
    private IPlugin _provider;

    /**
     * The class of the listener to inform about service availability. It
     * is used to instantiate the <code>listener</code> at the first time
     * the service is available.
     **/
    private String _listenerClass;

    /**
     * The listener object.
     **/
    private SoftDependencyListener _listener;

    /**
     * The plugin that wants to establish the soft dependency.
     **/
    private IPlugin _caller;

    /**
     * The plugin manager this soft dependency refers to.
     **/
    private PluginManager _mgr;

    /**
     * Establishes a <code>SoftDependency</code> from <code>caller</code>
     * plugin to the given <code>pluginService</code>.
     *
     * @param pluginService  the name of the service to listen for
     *
     * @param listenerClass  the class to inform about the service
     *                       availability.  It is resolved via reflection
     *                       to avoid class loading when the service is not
     *                       available.  Uses the bottom class loader of the
     *                       plugin system.
     *
     * @param caller         the plugin that wants to establish the soft
     *                       dependency
     **/
    public SoftDependency(IPlugin caller, String pluginService, String listenerClass) {
        this._caller = caller;
        this._pluginService = pluginService;
        this._listenerClass = listenerClass;
        this._listener = null;
        this._provider = null;
        this._mgr = PluginManager.getInstance();
        LOGGER.debug(
            "Soft dependency from " + caller + " to " + pluginService + " registered, using "
                + listenerClass + ".");
        _mgr.addPluginManagerListener(this);
        checkServiceAvailable();
    }

    /**
     * Removes the soft dependency. Discards the listener reference and
     * disconnects from the plugin system.
     * <p>
     * The listener is informed that the service is no longer available (if
     * it was available before), whatever the current state of the service
     * really is.
     * </p>
     **/
    public synchronized void discard() {
        LOGGER
            .debug("Unregistering soft dependency from " + _caller + " to " + _pluginService + ".");
        _mgr.removePluginManagerListener(this);
        fireServiceRemoved();
        _listener = null;
    }

    /**
     * Returns the <code>listener</code> object.
     * <p>
     * If this is the first call, the listener is instantiated from the
     * <code>listenerClass</code> name.
     * </p>
     * @return the listener object.
     *         Returns <code>null</code> if instantiation failed.
     **/
    public SoftDependencyListener getListener() {
        if (_listener == null) {
            try {
                Class<?> clazz = Class.forName(_listenerClass, true, _mgr.getBottomClassLoader());
                try {
                    Constructor<?> constructor =
                        clazz.getConstructor(new Class[] { IPlugin.class });
                    _listener =
                        (SoftDependencyListener) constructor.newInstance(new Object[] { _caller });
                } catch (NoSuchMethodException e) {
                    _listener = (SoftDependencyListener) clazz.getConstructor().newInstance();
                    LOGGER.debug(
                        "WARNING: Soft dependency from " + _caller + " to " + _pluginService
                            + ": listener is created with" + " no-arg constructor of " + clazz
                            + ".");
                }
                LOGGER.debug(
                    "Soft dependency from " + _caller + " to " + _pluginService + ": created "
                        + _listener + ".");
            } catch (InvocationTargetException e) {
                LOGGER.warn(
                    "WARNING: Soft dependency from " + _caller + " to " + _pluginService
                        + " could not be created: " + e.getTargetException() + ".");
                LOGGER.debug(e.getMessage(), e);
            } catch (Exception e) {
                LOGGER.warn(
                    "WARNING: Soft dependency from " + _caller + " to " + _pluginService
                        + " could not be created: " + e + ".");
                LOGGER.debug(e.getMessage(), e);
            } catch (ExceptionInInitializerError e) {
                LOGGER.warn(
                    "WARNING: Soft dependency from " + _caller + " to " + _pluginService
                        + " could not be created: " + e.getException() + ".");
                LOGGER.debug(e.getMessage(), e);
            }
        }
        return _listener;
    }

    /**
     * Calls <code>fireServiceAvailable</code> or <code>fireServiceRemoved</code>
     * based on the current state of the service availability.
     **/
    private void checkServiceAvailable() {
        Collection<IPlugin> providers = _mgr.getPluginsProviding(_pluginService);
        if (!providers.isEmpty()) {
            fireServiceAvailable(providers.iterator().next());
        } else {
            fireServiceRemoved();
        }
    }

    /**
     * Informs the listener about service availability, if the listener
     * exists (or can be instantiated) unless it has been informed about
     * service availability before.
     *
     * @param provider  a plugin that provides the service.
     **/
    private void fireServiceAvailable(IPlugin provider) {
        if ((this._provider == null) && (getListener() != null)) {
            LOGGER.debug("Soft dependency from " + _caller + " to " + provider + " activated.");
            _listener.serviceAvailable(provider);
            this._provider = provider;
        }
    }

    /**
     * Informs the listener about service removal, if the listener exists
     * and has been informed about service availability once before.
     **/
    private void fireServiceRemoved() {
        if ((this._provider != null) && (_listener != null)) {
            LOGGER.debug("Soft dependency from " + _caller + " to " + _provider + " deactivated.");
            getListener().serviceRemoved(_provider);
            this._provider = null;
        }
    }

    /**
     * To be called by the plugin manager only.
     **/
    @Override
    public synchronized void serviceAdded(final String service, IPlugin provider) {
        if (service.equals(_pluginService)) {
            Collection<IPlugin> providers = _mgr.getPluginsProviding(_pluginService);
            assert !providers.isEmpty()
                : "PluginManager misinformed soft dependency from " + _caller + " to " + service;
            fireServiceAvailable(providers.iterator().next());
        }
    }

    /**
     * To be called by the plugin manager only.
     **/
    @Override
    public synchronized void serviceRemoved(String service, IPlugin provider) {
        if (service.equals(_pluginService)) {
            fireServiceRemoved();
        }
    }
}