package org.infinispan.osgi;

import java.io.InputStream;
import java.net.URL;
import java.util.Dictionary;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Hashtable;
import java.util.Map;
import java.util.Set;

import org.infinispan.commons.util.FileLookupFactory;
import org.infinispan.manager.DefaultCacheManager;
import org.infinispan.manager.EmbeddedCacheManager;
import org.osgi.framework.BundleContext;
import org.osgi.framework.ServiceRegistration;
import org.osgi.service.cm.ConfigurationException;
import org.osgi.service.cm.ManagedServiceFactory;

public class InfinispanEmbeddedServiceFactory implements ManagedServiceFactory {
   /**
    * Configuration property used to indicate the cache configuration to use.
    */
    private static final String PROP_CONFIG = "config";

    /**
     * Configuration property used to identify the service created (e.g. for service filters).
     */
    private static final String PROP_INSTANCE_ID = "instanceId";

    private BundleContext bundleContext;
    private Map<String, DefaultCacheManager> cacheManagers = new HashMap<String, DefaultCacheManager>();
    private Map<String, ServiceRegistration<EmbeddedCacheManager>> managedRegistrations = new HashMap<String, ServiceRegistration<EmbeddedCacheManager>>();

    @Override
    public String getName() {
        return "Infinispan Embedded Managed Service Factory";
    }

    @Override
    public synchronized void updated(String pid, @SuppressWarnings("rawtypes") Dictionary properties) throws ConfigurationException {
        String config = (String) properties.get(PROP_CONFIG);
        if (config == null) {
            throw new ConfigurationException(PROP_CONFIG, "Property must be set");
        }

        String instanceId = (String) properties.get(PROP_INSTANCE_ID);
        if (instanceId == null) {
            throw new ConfigurationException(PROP_INSTANCE_ID, "Property must be set");
        }

        try {
            URL configURL = FileLookupFactory.newInstance().lookupFileLocation(config, Thread.currentThread().getContextClassLoader());
            if (configURL == null) {
                throw new ConfigurationException(PROP_CONFIG, String.format("Failed to find the specified config '%s'.", config));
            }

            /* Unregister and destroy the old object. */
            deleted(pid);

            InputStream configStream = configURL.openStream();
            DefaultCacheManager cacheManager = new DefaultCacheManager(configStream);

            cacheManager.start();

            Hashtable<String, String> ht = new Hashtable<String, String>();
            ht.put(PROP_INSTANCE_ID, instanceId);
            ht.put(PROP_CONFIG, config);

            ServiceRegistration<EmbeddedCacheManager> serviceRegistration = bundleContext.registerService(EmbeddedCacheManager.class, cacheManager, ht);

            managedRegistrations.put(pid, serviceRegistration);
            cacheManagers.put(pid, cacheManager);
        } catch (Exception e) {
            throw new ConfigurationException(null, "Cannot start the CacheManager", e);
        }
    }

    @Override
    public synchronized void deleted(String pid) {
        ServiceRegistration<EmbeddedCacheManager> serviceRegistration = managedRegistrations.remove(pid);
        if (serviceRegistration != null) {
            try {
                serviceRegistration.unregister();
            } catch (Exception e) {
            }
        }

        DefaultCacheManager cacheManager = cacheManagers.remove(pid);
        if (cacheManager != null) {
            try {
                cacheManager.stop();
            } catch (Exception e) {
            }
        }
    }

    public synchronized void destroy() {
        /* Create a copy of the keyset before iterating over it to avoid ConcurrentModificationExceptions. */
        Set<String> keys = new HashSet<String>(cacheManagers.keySet());
        for (String cacheManager: keys) {
            deleted(cacheManager);
        }
    }

    public BundleContext getBundleContext() {
        return bundleContext;
    }

    public void setBundleContext(BundleContext bundleContext) {
        this.bundleContext = bundleContext;
    }
}
