/*
 * Copyright (c) 2007, Red Hat Middleware, LLC. All rights reserved.
 *
 * This copyrighted material is made available to anyone wishing to use, modify,
 * copy, or redistribute it subject to the terms and conditions of the GNU
 * Lesser General Public License, v. 2.1. This program is distributed in the
 * hope that it will be useful, but WITHOUT A 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, v.2.1 along with this
 * distribution; if not, write to the Free Software Foundation, Inc.,
 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
 *
 * Red Hat Author(s): Brian Stansberry
 */

package org.jboss.cache;

import org.jboss.cache.config.Configuration;
import org.jboss.cache.config.ConfigurationRegistry;
import org.jboss.cache.config.XmlParsingConfigurationRegistry;
import org.jgroups.ChannelFactory;

import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.Map;
import java.util.Set;

/**
 * Basic implementation of {@link CacheManager}.
 *
 * @author <a href="brian.stansberry@jboss.com">Brian Stansberry</a>
 * @version $Revision: 1 $
 */
public class CacheManagerImpl implements CacheManager
{
   private ConfigurationRegistry configRegistry;

   private boolean configRegistryInjected;

   private final Map<String, Cache<Object, Object>> caches = new HashMap<String, Cache<Object, Object>>();

   private final Map<String, Integer> checkouts = new HashMap<String, Integer>();

   private ChannelFactory channelFactory;

   private boolean started;

   /**
    * Create a new CacheRegistryImpl.
    */
   public CacheManagerImpl()
   {
   }

   /**
    * Create a new CacheRegistryImpl using the provided ConfigurationRegistry
    * and ChannelFactory.
    */
   public CacheManagerImpl(ConfigurationRegistry configRegistry, ChannelFactory factory)
   {
      this.configRegistry = configRegistry;
      this.configRegistryInjected = true;
      this.channelFactory = factory;
   }

   /**
    * Create a new CacheRegistryImpl using the provided ChannelFactory and
    * using the provided file name to create an
    * {@link XmlParsingConfigurationRegistry}.
    */
   public CacheManagerImpl(String configFileName, ChannelFactory factory)
   {
      configRegistry = new XmlParsingConfigurationRegistry(configFileName);
      this.channelFactory = factory;
   }

   // ----------------------------------------------------------  CacheRegistry

   public ChannelFactory getChannelFactory()
   {
      return channelFactory;
   }

   @SuppressWarnings("unchecked")
   public Set<String> getConfigurationNames()
   {
      synchronized (caches)
      {
         Set<String> configNames = configRegistry == null ? new HashSet<String>()
               : configRegistry.getConfigurationNames();
         configNames.addAll(getCacheNames());
         return configNames;
      }
   }

   public Set<String> getCacheNames()
   {
      synchronized (caches)
      {
         return new HashSet<String>(caches.keySet());
      }
   }

   public Cache<Object, Object> getCache(String configName, boolean create) throws Exception
   {
      Cache<Object, Object> cache;
      synchronized (caches)
      {
         cache = caches.get(configName);
         if (cache == null && create)
         {
            Configuration config = configRegistry.getConfiguration(configName);
            if (channelFactory != null && config.getMultiplexerStack() != null)
            {
               config.getRuntimeConfig().setMuxChannelFactory(channelFactory);
            }
            cache = createCache(config);
            registerCache(cache, configName);
         }
         else if (cache != null)
         {
            incrementCheckout(configName);
         }
      }

      return cache;
   }

   /**
    * Extension point for subclasses, where we actually use a
    * {@link CacheFactory} to create a cache. This default implementation
    * uses {@link DefaultCacheFactory}.
    *
    * @param config the Configuration for the cache
    * @return the Cache
    */
   @SuppressWarnings("unchecked")
   protected Cache<Object, Object> createCache(Configuration config)
   {
      return new DefaultCacheFactory().createCache(config, false);
   }

   public void releaseCache(String configName)
   {
      synchronized (caches)
      {
         if (!caches.containsKey(configName))
            throw new IllegalStateException(configName + " not registered");
         if (decrementCheckout(configName) == 0)
         {
            Cache<Object, Object> cache = caches.remove(configName);
            destroyCache(cache);
         }
      }
   }

   // -----------------------------------------------------------------  Public

   public ConfigurationRegistry getConfigurationRegistry()
   {
      return configRegistry;
   }

   public void setConfigurationRegistry(ConfigurationRegistry configRegistry)
   {
      this.configRegistry = configRegistry;
      this.configRegistryInjected = true;
   }

   public void setChannelFactory(ChannelFactory channelFactory)
   {
      this.channelFactory = channelFactory;
   }

   public void registerCache(Cache<Object, Object> cache, String configName)
   {
      synchronized (caches)
      {
         if (caches.containsKey(configName))
            throw new IllegalStateException(configName + " already registered");
         caches.put(configName, cache);
         incrementCheckout(configName);
      }
   }

   public void start() throws Exception
   {
      if (!started)
      {
         if (configRegistry == null)
            throw new IllegalStateException("Must configure a ConfigurationRegistry before calling start()");
         if (channelFactory == null)
            throw new IllegalStateException("Must provide a ChannelFactory before calling start()");

         if (!configRegistryInjected)
         {
            ((XmlParsingConfigurationRegistry) configRegistry).start();
         }

         started = true;
      }
   }

   public void stop()
   {
      if (started)
      {
         synchronized (caches)
         {
            for (Iterator<Map.Entry<String, Cache<Object, Object>>> it = caches.entrySet().iterator(); it.hasNext();)
            {
               Map.Entry<String, Cache<Object, Object>> entry = it.next();
               destroyCache(entry.getValue());
               it.remove();
            }
            caches.clear();
            checkouts.clear();
         }

         if (!configRegistryInjected)
         {
            ((XmlParsingConfigurationRegistry) configRegistry).stop();
         }

         started = false;
      }
   }

   // ----------------------------------------------------------------  Private

   private int incrementCheckout(String configName)
   {
      synchronized (checkouts)
      {
         Integer count = checkouts.get(configName);
         if (count == null)
            count = 0;
         Integer newVal = count + 1;
         checkouts.put(configName, newVal);
         return newVal;
      }
   }

   private int decrementCheckout(String configName)
   {
      synchronized (checkouts)
      {
         Integer count = checkouts.get(configName);
         if (count == null || count < 1)
            throw new IllegalStateException("invalid count of " + count + " for " + configName);

         Integer newVal = count - 1;
         checkouts.put(configName, newVal);
         return newVal;
      }
   }

   private void destroyCache(Cache<Object, Object> cache)
   {
      if (cache.getCacheStatus() == CacheStatus.STARTED)
      {
         cache.stop();
      }
      if (cache.getCacheStatus() != CacheStatus.DESTROYED && cache.getCacheStatus() != CacheStatus.INSTANTIATED)
      {
         cache.destroy();
      }
   }
}
