/*
 * Copyright 2010 Niclas Hedhman.
 *
 * 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.qi4j.cache.ehcache;

import java.util.concurrent.ConcurrentHashMap;
import net.sf.ehcache.CacheManager;
import net.sf.ehcache.config.CacheConfiguration;
import net.sf.ehcache.config.PersistenceConfiguration;
import net.sf.ehcache.config.PersistenceConfiguration.Strategy;
import org.qi4j.api.common.Optional;
import org.qi4j.api.configuration.Configuration;
import org.qi4j.api.injection.scope.This;
import org.qi4j.api.util.NullArgumentException;
import org.qi4j.spi.cache.Cache;

public abstract class EhCachePoolMixin
    implements EhCachePoolService
{

    private final ConcurrentHashMap<String, EhCacheImpl<?>> caches = new ConcurrentHashMap<>();
    @This @Optional
    private Configuration<EhCacheConfiguration> config;
    private CacheManager cacheManager;

    @Override
    @SuppressWarnings( "unchecked" )
    public <T> Cache<T> fetchCache( String cacheId, Class<T> valueType )
    {
        // Note: Small bug in Ehcache; If the cache name is an empty String it will actually work until
        //       you try to remove the Cache instance from the CacheManager, at which point it is silently
        //       ignored but not removed so there is an follow up problem of too much in the CacheManager.
        NullArgumentException.validateNotEmpty( "cacheId", cacheId );
        EhCacheImpl<?> cache = caches.get( cacheId );
        if( cache == null )
        {
            cache = createNewCache( cacheId, valueType );
            caches.put( cacheId, cache );
        }
        cache.incRefCount();
        return (Cache<T>) cache;
    }

    private <T> EhCacheImpl<T> createNewCache( String cacheId, Class<T> valueType )
    {
        CacheConfiguration cc = createCacheConfiguration( cacheId );

        // TODO: We also need all the other Configurations that are possible, like cacheLoaderFactoryConfiguration
        net.sf.ehcache.Cache cache = new net.sf.ehcache.Cache( cc );
        cacheManager.addCache( cache );

        return new EhCacheImpl<>( cacheId, cache, valueType );
    }

    @Override
    public void returnCache( Cache<?> cache )
    {
        EhCacheImpl<?> eh = (EhCacheImpl<?>) cache;
        eh.decRefCount();
        if( eh.isNotUsed() )
        {
            caches.remove( eh.cacheId() );
            cacheManager.removeCache( eh.cacheId() );
        }
    }

    @Override
    public void activateService()
        throws Exception
    {
        net.sf.ehcache.config.Configuration configuration = new net.sf.ehcache.config.Configuration();
        configureEhCache( configuration );
        CacheConfiguration cc = createCacheConfiguration( "qi4j.ehcache.config.default" );
        configuration.setDefaultCacheConfiguration( cc );
        cacheManager = CacheManager.newInstance( configuration );
    }

    @Override
    public void passivateService()
        throws Exception
    {
        cacheManager.shutdown();
    }

    private void configureEhCache( net.sf.ehcache.config.Configuration configuration )
    {
        EhCacheConfiguration conf = config.get();
        Boolean updateCheck = conf.updateCheck().get();
        configuration.setUpdateCheck( updateCheck );
        configuration.setDynamicConfig( true );
        String monitoring = conf.monitoring().get().trim();
        if( monitoring.length() > 0 )
        {
            configuration.setMonitoring( monitoring );
        }
        String name = conf.cacheManagerName().get();
        if( name == null )
        {
            name = "Zest Cache Extension";
        }
        configuration.setName( name );
        String diskStorePath = conf.diskStorePath().get();
        if( diskStorePath.length() > 0 )
        {
            configuration.getDiskStoreConfiguration().path( diskStorePath );
        }
    }

    private CacheConfiguration createCacheConfiguration( String cacheId )
    {
        EhCacheConfiguration conf = config.get();
        Integer maxElementsInMemory = conf.maxElementsInMemory().get();
        if( maxElementsInMemory <= 0 )
        {
            maxElementsInMemory = 10000;
        }
        CacheConfiguration cacheConfig = new CacheConfiguration( cacheId, maxElementsInMemory );
        String transactionalMode = conf.transactionalMode().get();
        if( transactionalMode.length() > 0 )
        {
            cacheConfig.transactionalMode( transactionalMode );
        }

        Long timeToLiveSeconds = conf.timeToLiveSeconds().get();
        if( timeToLiveSeconds > 0 )
        {
            cacheConfig.timeToLiveSeconds( timeToLiveSeconds );
        }

        Long timeToIdleSeconds = conf.timeToIdleSeconds().get();
        if( timeToIdleSeconds > 0 )
        {
            cacheConfig.timeToIdleSeconds( timeToIdleSeconds );
        }

        String name = conf.name().get();
        if( name.length() > 0 )
        {
            cacheConfig.name( name );
        }

        String memoryStoreEvictionPolicy = conf.memoryStoreEvictionPolicy().get();
        if( memoryStoreEvictionPolicy.length() > 0 )
        {
            cacheConfig.memoryStoreEvictionPolicy( memoryStoreEvictionPolicy );
        }

        Integer maxElementsOnDisk = conf.maxElementsOnDisk().get();
        if( maxElementsOnDisk > 0 )
        {
            cacheConfig.maxElementsOnDisk( maxElementsOnDisk );
        }

        Boolean loggingEnabled = conf.loggingEnabled().get();
        if( loggingEnabled != null )
        {
            cacheConfig.logging( loggingEnabled );
        }

        Boolean eternal = conf.eternal().get();
        cacheConfig.eternal( eternal );

        Integer diskSpoolBufferSizeMB = conf.diskSpoolBufferSizeMB().get();
        if( diskSpoolBufferSizeMB > 0 )
        {
            cacheConfig.diskSpoolBufferSizeMB( diskSpoolBufferSizeMB );
        }

        Long diskExpiryThreadIntervalSeconds = conf.diskExpiryThreadIntervalSeconds().get();
        if( diskExpiryThreadIntervalSeconds > 0 )
        {
            cacheConfig.diskExpiryThreadIntervalSeconds( diskExpiryThreadIntervalSeconds );
        }

        Integer diskAccessStripes = conf.diskAccessStripes().get();
        if( diskAccessStripes > 0 )
        {
            cacheConfig.diskAccessStripes( diskAccessStripes );
        }
        Boolean clearOnFlush = conf.clearOnFlush().get();
        if( clearOnFlush != null )
        {
            cacheConfig.clearOnFlush( clearOnFlush );
        }

        // Persistence Configuration
        PersistenceConfiguration persistenceConfig = new PersistenceConfiguration();
        Strategy strategy = conf.persistenceStrategy().get();
        if( strategy == null )
        {
            persistenceConfig.strategy( Strategy.NONE );
        }
        else
        {
            persistenceConfig.strategy( strategy );
        }
        cacheConfig.persistence( persistenceConfig );

        return cacheConfig;
    }

}
