package org.infinispan.jcache.remote;

import java.io.IOException;
import java.io.InputStream;
import java.net.URI;
import java.util.Objects;
import java.util.Properties;

import javax.cache.Cache;
import javax.cache.CacheException;
import javax.cache.configuration.Configuration;
import javax.cache.spi.CachingProvider;

import org.infinispan.client.hotrod.RemoteCache;
import org.infinispan.client.hotrod.RemoteCacheManager;
import org.infinispan.client.hotrod.configuration.ConfigurationBuilder;
import org.infinispan.commons.api.BasicCache;
import org.infinispan.commons.logging.LogFactory;
import org.infinispan.commons.util.FileLookupFactory;
import org.infinispan.commons.util.ReflectionUtil;
import org.infinispan.jcache.AbstractJCache;
import org.infinispan.jcache.AbstractJCacheManager;
import org.infinispan.jcache.logging.Log;

public class JCacheManager extends AbstractJCacheManager {
   private static final Log log = LogFactory.getLog(JCacheManager.class, Log.class);

   private RemoteCacheManager cm;
   private RemoteCacheManager cmForceReturnValue;

   private RemoteCacheAccess cacheAccess;

   public JCacheManager(URI uri, ClassLoader classLoader, CachingProvider provider, Properties properties) {
      super(uri, classLoader, provider, properties, false);

      ConfigurationBuilder builder = getConfigurationBuilder(properties);

      org.infinispan.client.hotrod.configuration.Configuration configuration = builder.build();
      cm = new RemoteCacheManager(configuration, true);

      builder.forceReturnValues(true);
      cmForceReturnValue = new RemoteCacheManager(builder.build(), true);

      cacheAccess = RemoteCacheAccess.createCacheAccess(cm, properties);
   }

   private ConfigurationBuilder getConfigurationBuilder(Properties userProperties) {
      ConfigurationBuilder builder = new ConfigurationBuilder();
      InputStream is;
      if (userProperties != null && !userProperties.isEmpty()) {
         this.properties = userProperties;
         builder.withProperties(userProperties);
      } else if ((is = findPropertiesFile()) != null) {
         Properties fileProperties = new Properties();
         try {
            fileProperties.load(is);
            this.properties = fileProperties;
            builder.withProperties(fileProperties);
         } catch (IOException e) {
            throw new CacheException("Unable to load properties from `hotrod-client.properties`", e);
         }
      } else {
         builder.addServer().host("127.0.0.1").port(11222);
      }
      return builder;
   }

   private InputStream findPropertiesFile() {
      ClassLoader cl = Thread.currentThread().getContextClassLoader();
      return FileLookupFactory.newInstance()
            .lookupFile(RemoteCacheManager.HOTROD_CLIENT_PROPERTIES, cl);
   }

   @Override
   public ClassLoader getClassLoader() {
      return cm.getConfiguration().classLoader();
   }

   @Override
   protected <K, V, C extends Configuration<K, V>> AbstractJCache<K, V> create(String cacheName, C configuration) {
      ConfigurationAdapter<K, V> adapter = ConfigurationAdapter.create(configuration);

      cacheAccess.addCacheIfAbsent(cacheName);
      return createJCache(cacheName, adapter);
   }

   private <K, V> AbstractJCache<K, V> createJCache(String cacheName, ConfigurationAdapter<K, V> adapter) {
      RemoteCache<K, V> ispnCache = getRemoteCache(cm, cacheName);
      RemoteCache<K, V> ispnCacheForceReturnValue = getRemoteCache(cmForceReturnValue, cacheName);
      return new JCache<K, V>(ispnCache, ispnCacheForceReturnValue, this, adapter);
   }

   private <K, V> RemoteCache<K, V> getRemoteCache(RemoteCacheManager cm, String cacheName) {
      RemoteCache<K, V> ispnCache = cm.getCache(cacheName);
      if (ispnCache == null) {
         throw log.cacheNotFound(cacheName);
      }
      return ispnCache;
   }

   @Override
   public <K, V> Cache<K, V> getCache(String cacheName) {
      Cache<K, V> cache = super.getCache(cacheName);
      return Objects.isNull(cache)
            ? createRegisterJCache(cacheName)
            : cache;
   }

   public <K, V> Cache<K, V> createRegisterJCache(String cacheName) {
      RemoteCache<K, V> ispnCache = cm.getCache(cacheName);
      RemoteCache<K, V> ispnCacheForceReturnValue = cmForceReturnValue.getCache(cacheName);
      if (ispnCache != null && cmForceReturnValue != null) {
         ConfigurationAdapter<K, V> adapter = ConfigurationAdapter.create();
         JCache<K, V> jcache = new JCache<>(ispnCache, ispnCacheForceReturnValue, this, adapter);
         registerPredefinedCache(cacheName, jcache);
         return jcache;
      }
      return null;
   }

   @Override
   public <K, V> Cache<K, V> getCache(String cacheName, Class<K> keyType, Class<V> valueType) {
      Cache<K, V> cache = super.getCache(cacheName, keyType, valueType);
      return Objects.isNull(cache)
            ? createRegisterJCache(cacheName)
            : cache;
   }

   @Override
   protected <K, V, I extends BasicCache<K, V>> AbstractJCache<K, V> create(I ispnCache) {
      return null;
      //FIXME implement me
   }

   @Override
   public <T> T unwrap(Class<T> clazz) {
      return ReflectionUtil.unwrap(this, clazz);
   }

   @Override
   protected Iterable<String> delegateCacheNames() {
      return getManagedCacheNames();
   }

   @Override
   protected void delegateStop() {
      cm.stop();
      cmForceReturnValue.stop();
   }

   @Override
   protected boolean delegateIsClosed() {
      return false;
      //FIXME implement me
   }

   @Override
   protected <K, V> void delegateRemoveCache(AbstractJCache<K, V> jcache) {
      cacheAccess.removeCache(jcache.getName());
      jcache.close();
   }

   @Override
   protected void delegateLogIsClosed() {
      throw log.cacheClosed();
   }
}
