/*
 * Licensed 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.apache.aries.subsystem.core.internal;

import java.util.Collection;
import java.util.Collections;
import java.util.Dictionary;
import java.util.HashSet;
import java.util.Hashtable;

import org.apache.aries.subsystem.ContentHandler;
import org.apache.aries.subsystem.core.content.ConfigAdminContentHandler;
import org.apache.aries.util.filesystem.IDirectoryFinder;
import org.eclipse.equinox.region.RegionDigraph;
import org.osgi.framework.BundleActivator;
import org.osgi.framework.BundleContext;
import org.osgi.framework.Filter;
import org.osgi.framework.FrameworkUtil;
import org.osgi.framework.InvalidSyntaxException;
import org.osgi.framework.ServiceReference;
import org.osgi.framework.ServiceRegistration;
import org.osgi.framework.hooks.bundle.EventHook;
import org.osgi.framework.hooks.resolver.ResolverHookFactory;
import org.osgi.service.coordinator.Coordinator;
import org.osgi.service.resolver.Resolver;
import org.osgi.util.tracker.ServiceTracker;
import org.osgi.util.tracker.ServiceTrackerCustomizer;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
 * The bundle activator for the this bundle. When the bundle is starting, this
 * activator will create and register the SubsystemAdmin service.
 */
public class Activator implements BundleActivator, ServiceTrackerCustomizer<Object, Object> {
    private static final Logger logger = LoggerFactory.getLogger(Activator.class);
    public static final String MODELLED_RESOURCE_MANAGER = "org.apache.aries.subsystem.modelling.ModelledResourceManager";
    private static final String LOCK_TIMEOUT = "org.apache.aries.subsystem.lock.timeout";

    public static final String LOG_ENTRY = "Method entry: {}, args {}";
    public static final String LOG_EXIT = "Method exit: {}, returning {}";

    private static volatile Activator instance;
	public static Activator getInstance() {
	    Activator result = instance;
	    if (result == null)
            throw new IllegalStateException("The activator has not been initialized or has been shutdown");
		return result;
	}
	
	private volatile BundleContext bundleContext;
	private volatile LockingStrategy lockingStrategy;
    private volatile ConfigAdminContentHandler configAdminHandler;
	private volatile Coordinator coordinator;
    private volatile Object modelledResourceManager;
    private volatile RegionDigraph regionDigraph;
	private volatile SubsystemServiceRegistrar registrar;
	private volatile Resolver resolver;
	private volatile ServiceModeller serviceModeller;
	private volatile Subsystems subsystems;
	private volatile SystemRepositoryManager systemRepositoryManager;
	
	private BundleEventHook bundleEventHook;
	private ServiceTracker<?,?> serviceTracker;

	private final Collection<IDirectoryFinder> finders = Collections.synchronizedSet(new HashSet<IDirectoryFinder>());
	private final Collection<ServiceRegistration<?>> registrations = new HashSet<ServiceRegistration<?>>();
	
	public BundleContext getBundleContext() {
		return bundleContext;
	}

	public LockingStrategy getLockingStrategy() {
		return lockingStrategy;
	}

	public Coordinator getCoordinator() {
		return coordinator;
	}

    public ServiceModeller getServiceModeller() {
        return serviceModeller;
    }

    public RegionDigraph getRegionDigraph() {
		return regionDigraph;
	}

	public Collection<IDirectoryFinder> getIDirectoryFinders() {
		return Collections.unmodifiableCollection(finders);
	}

	public Resolver getResolver() {
		return resolver;
	}

	public Subsystems getSubsystems() {
		return subsystems;
	}

	public SubsystemServiceRegistrar getSubsystemServiceRegistrar() {
		logger.debug(LOG_ENTRY, "getSubsystemServiceRegistrar");
		SubsystemServiceRegistrar result = registrar;
		logger.debug(LOG_EXIT, "getSubsystemServiceRegistrar", result);
		return result;
	}

	public SystemRepository getSystemRepository() {
		return systemRepositoryManager.getSystemRepository();
	}

	@Override
	public synchronized void start(BundleContext context) throws Exception {
		logger.debug(LOG_ENTRY, "start", context);
		bundleContext = context;
		lockingStrategy = new LockingStrategy(bundleContext.getProperty(LOCK_TIMEOUT));
		serviceTracker = new ServiceTracker<Object, Object>(bundleContext, generateServiceFilter(), this);
		serviceTracker.open();
		logger.debug(LOG_EXIT, "start");
	}

	@Override
	public synchronized void stop(BundleContext context) {
		logger.debug(LOG_ENTRY, "stop", context);
		serviceTracker.close();
		serviceTracker = null;
		bundleContext = null;
		logger.debug(LOG_EXIT, "stop");
	}

	private void activate() {
		if (isActive() || !hasRequiredServices())
			return;
		synchronized (Activator.class) {
			instance = Activator.this;
		}
		subsystems = new Subsystems();
		registerBundleEventHook();
		registrations.add(bundleContext.registerService(ResolverHookFactory.class, new SubsystemResolverHookFactory(subsystems), null));
        Dictionary<String, Object> handlerProps = new Hashtable<String, Object>();
        handlerProps.put(ContentHandler.CONTENT_TYPE_PROPERTY, ConfigAdminContentHandler.CONTENT_TYPES);
        configAdminHandler = new ConfigAdminContentHandler(bundleContext);
        registrations.add(bundleContext.registerService(ContentHandler.class, configAdminHandler, handlerProps));
		registrar = new SubsystemServiceRegistrar(bundleContext);
		systemRepositoryManager = new SystemRepositoryManager(bundleContext.getBundle(0).getBundleContext());
        systemRepositoryManager.open();
		BasicSubsystem root = subsystems.getRootSubsystem();
		bundleEventHook.activate();
		root.start();
		registerWovenClassListener();
	}

	private void deactivate() {
		if (!isActive())
			return;
		bundleEventHook.deactivate();
		systemRepositoryManager.close();
		new StopAction(subsystems.getRootSubsystem(), subsystems.getRootSubsystem(), true).run();
		for (ServiceRegistration<?> registration : registrations) {
			try {
				registration.unregister();
			}
			catch (IllegalStateException e) {
				logger.debug("Service had already been unregistered", e);
			}
		}
        configAdminHandler.shutDown();
		bundleEventHook.processPendingEvents();
		synchronized (Activator.class) {
			instance = null;
		}
	}

	private <T> T findAlternateServiceFor(Class<T> service) {
		Object[] services = serviceTracker.getServices();
		if (services == null)
			return null;
		for (Object alternate : services)
			if (service.isInstance(alternate))
					return service.cast(alternate);
		return null;
	}

	private Filter generateServiceFilter() throws InvalidSyntaxException {
		return FrameworkUtil.createFilter(generateServiceFilterString());
	}

	private String generateServiceFilterString() {
		return new StringBuilder("(|(")
				.append(org.osgi.framework.Constants.OBJECTCLASS).append('=')
				.append(Coordinator.class.getName()).append(")(")
				.append(org.osgi.framework.Constants.OBJECTCLASS).append('=')
				.append(RegionDigraph.class.getName()).append(")(")
				.append(org.osgi.framework.Constants.OBJECTCLASS).append('=')
				.append(Resolver.class.getName()).append(")(")
				.append(org.osgi.framework.Constants.OBJECTCLASS).append('=')
				.append("org.osgi.service.repository.Repository").append(")(")
				.append(org.osgi.framework.Constants.OBJECTCLASS).append('=')
				.append(MODELLED_RESOURCE_MANAGER).append(")(")
				.append(org.osgi.framework.Constants.OBJECTCLASS).append('=')
				.append(IDirectoryFinder.class.getName()).append("))").toString();
	}

	private boolean hasRequiredServices() {
		return coordinator != null &&
				regionDigraph != null &&
				resolver != null;
	}

	private boolean isActive() {
		synchronized (Activator.class) {
			return instance != null && getSubsystems() != null;
		}
	}

	private void registerBundleEventHook() {
		Dictionary<String, Object> properties = new Hashtable<String, Object>(1);
		properties.put(org.osgi.framework.Constants.SERVICE_RANKING, Integer.MAX_VALUE);
		bundleEventHook = new BundleEventHook();
		registrations.add(bundleContext.registerService(EventHook.class, bundleEventHook, properties));
	}

	private void registerWovenClassListener() {
		registrations.add(
				bundleContext.registerService(
						org.osgi.framework.hooks.weaving.WovenClassListener.class,
						new WovenClassListener(bundleContext, subsystems),
						null));
	}
	
	/* Begin ServiceTrackerCustomizer methods */

	@Override
	public synchronized Object addingService(ServiceReference<Object> reference) {
		Object service = bundleContext.getService(reference);
		// Use all of each type of the following services.
		if (service instanceof IDirectoryFinder)
			finders.add((IDirectoryFinder) service);
		// Use only one of each type of the following services.
		else if (service instanceof Coordinator && coordinator == null)
			coordinator = (Coordinator) service;
		else if (service instanceof RegionDigraph && regionDigraph == null)
			regionDigraph = (RegionDigraph) service;
		else if (service instanceof Resolver && resolver == null)
			resolver = (Resolver) service;
		else {
			try {
				Class clazz = getClass().getClassLoader().loadClass(MODELLED_RESOURCE_MANAGER);
				if (clazz.isInstance(service) && serviceModeller == null) {
					modelledResourceManager = service;
					serviceModeller = new ApplicationServiceModeller(service);
				} else {
					service = null;
				}
			} catch (ClassNotFoundException e) {
				service = null;
			} catch (NoClassDefFoundError e) {
				service = null;
			}
		}
		// Activation is harmless if already active or all required services
		// have not yet been found.
		activate();
		// Filter guarantees we want to track all services received.
		return service;
	}

	@Override
	public void modifiedService(ServiceReference<Object> reference, Object service) {
		// Nothing
	}

	@Override
	public synchronized void removedService(ServiceReference<Object> reference, Object service) {
		if (service instanceof Coordinator) {
			if (service.equals(coordinator)) {
				Coordinator coordinator = findAlternateServiceFor(Coordinator.class);
				if (coordinator == null)
					deactivate();
				this.coordinator = coordinator;
			}
		}
		else if (service instanceof RegionDigraph) {
			if (service.equals(regionDigraph)) {
				RegionDigraph regionDigraph = findAlternateServiceFor(RegionDigraph.class);
				if (regionDigraph == null)
					deactivate();
				this.regionDigraph = regionDigraph;
			}
		}
		else if (service instanceof Resolver) {
			if (service.equals(resolver)) {
				Resolver resolver = findAlternateServiceFor(Resolver.class);
				if (resolver == null)
					deactivate();
				this.resolver = resolver;
			}
		}
		else if (service instanceof IDirectoryFinder)
			finders.remove(service);
        else {
            if (service.equals(modelledResourceManager)) {
                try {
                    Class clazz = getClass().getClassLoader().loadClass(MODELLED_RESOURCE_MANAGER);
                    Object manager = findAlternateServiceFor(clazz);
                    if (manager == null) {
                        modelledResourceManager = null;
                        serviceModeller = null;
                    } else {
                        modelledResourceManager = service;
                        serviceModeller = new ApplicationServiceModeller(service);
                    }
                } catch (ClassNotFoundException e) {
                    // ignore
                } catch (NoClassDefFoundError e) {
                    // ignore
                }
            }
        }
	}

	/* End ServiceTrackerCustomizer methods */
}
