package biz.aQute.configadmin;

import java.io.IOException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.LinkedHashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;

import org.osgi.framework.BundleContext;
import org.osgi.framework.Filter;
import org.osgi.framework.FrameworkUtil;
import org.osgi.framework.InvalidSyntaxException;
import org.osgi.framework.ServiceRegistration;
import org.osgi.service.cm.Configuration;
import org.osgi.service.cm.ConfigurationAdmin;
import org.osgi.service.cm.ConfigurationException;
import org.osgi.service.cm.ManagedService;
import org.osgi.service.cm.ManagedServiceFactory;

/**
 * <p>
 * This is a simplified ConfigurationAdmin implementation that is intended for
 * statically configuring an OSGi runtime during startup. It should be used as
 * follows:
 * </p>
 * 
 * <ul>
 * <li>Load the configuration and instantiate a set of
 * {@link StaticConfiguration} objects.</li>
 * <li>Instantiate a {@code StaticConfigurationAdmin}.</li>
 * <li>Call {@link #start()}. This will register the object as a
 * {@link ConfigurationAdmin} service.</li>
 * <li>After all bundles are started, call {@link #updatedManagedServices()}.
 * This sends the {@code updated} signal to all registered
 * {@link ManagedService} and {@link ManagedServiceFactory} services.</li>
 * </ul>
 * 
 * <p>
 * Note that since the implementation is intended only for <em>static</em>
 * configuration, it does not support any methods for creating, modifying or
 * deleting configs. Any attempt to call these methods (including
 * {@link #getConfiguration(String)} with a not-already-existing PID) will throw
 * a {@link SecurityException}.
 * </p>
 * 
 * @author Neil Bartlett
 */
public class StaticConfigurationAdmin implements ConfigurationAdmin {
	
	private final BundleContext context;
	private final Map<String, StaticConfiguration> configsMap = new LinkedHashMap<String, StaticConfiguration>();

	private ServiceRegistration serviceReg;
	private PidTracker<ManagedService> msTracker;
	private PidTracker<ManagedServiceFactory> msfTracker;

	public StaticConfigurationAdmin(BundleContext context, Collection<StaticConfiguration> configs) {
		this.context = context;
		for (StaticConfiguration config : configs) {
			String pid = config.getPid();
			
			if (configsMap.containsKey(pid))
				throw new IllegalArgumentException("Duplicate configuration pid: " + pid);
			configsMap.put(pid, config);
		}
	}

	public void start() {
		serviceReg = context.registerService(ConfigurationAdmin.class.getName(), this, null);
		
		msTracker = new PidTracker<ManagedService>(context, ManagedService.class);
		msTracker.open();

		msfTracker = new PidTracker<ManagedServiceFactory>(context, ManagedServiceFactory.class);
		msfTracker.open();
	}
	
	/**
	 * Sends the configuration to all currently registered ManagedService and ManagedServiceFactory
	 * services. Should be called after all bundles have been started.
	 */
	public List<ConfigurationException> updatedManagedServices() {
		List<ConfigurationException> exceptions = new LinkedList<ConfigurationException>();

		for (Configuration config : configsMap.values()) {
			String factoryPid = config.getFactoryPid();
			if (factoryPid != null) {
				ManagedServiceFactory msf = msfTracker.findPid(factoryPid);
				if (msf != null) {
					try {
						msf.updated(factoryPid, config.getProperties());
					} catch (ConfigurationException e) {
						exceptions.add(e);
					}
				}
			} else {
				ManagedService ms = msTracker.findPid(config.getPid());
				if (ms != null) {
					try {
						ms.updated(config.getProperties());
					} catch (ConfigurationException e) {
						exceptions.add(e);
					}
				}
			}
		}

		return exceptions;
	}

	public void stop() {
		msfTracker.close();
		msfTracker = null;

		msTracker.close();
		msTracker = null;

		serviceReg.unregister();
		serviceReg = null;
	}

	@Override
	public Configuration createFactoryConfiguration(String factoryPid) throws IOException {
		throw new SecurityException("Cannot create configurations");
	}

	@Override
	public Configuration createFactoryConfiguration(String factoryPid, String location) throws IOException {
		throw new SecurityException("Cannot create configurations");
	}

	@Override
	public Configuration getConfiguration(String pid, String location) throws IOException {
		StaticConfiguration config = configsMap.get(pid);
		if (config == null)
			throw new SecurityException("Cannot create configurations");

		return config;
	}

	@Override
	public Configuration getConfiguration(String pid) throws IOException {
		return getConfiguration(pid, null);
	}

	@Override
	public Configuration[] listConfigurations(String filterStr) throws IOException, InvalidSyntaxException {
		Filter filter = filterStr != null ? FrameworkUtil.createFilter(filterStr) : null;
		
		List<StaticConfiguration> result = new ArrayList<StaticConfiguration>(configsMap.size());
		for (StaticConfiguration config : configsMap.values()) {
			if (filter == null || filter.match(config.getProperties()))
				result.add(config);
		}
		
		return result.toArray(new Configuration[result.size()]);
	}

}
