/*
 * Copyright (c) 2012-2019 Jakub Białek
 * 
 * Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated
 * documentation files (the "Software"), to deal in the Software without restriction, including without limitation the
 * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to
 * permit persons to whom the Software is furnished to do so, subject to the following conditions:
 * 
 * The above copyright notice and this permission notice shall be included in all copies or substantial portions of the
 * Software.
 * 
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
 * WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
 * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
 * OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
 */
package com.google.code.ssm.spring;

import java.util.Collection;
import java.util.Collections;
import java.util.LinkedHashSet;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;

import lombok.Getter;
import lombok.Setter;

import org.springframework.beans.factory.InitializingBean;
import org.springframework.cache.Cache;
import org.springframework.cache.CacheManager;
import org.springframework.util.Assert;
import org.springframework.util.CollectionUtils;

import com.google.code.ssm.PrefixedCacheImpl;

/**
 * 
 * CacheManager backed by a Simple Spring Memcached (SSM) {@link com.google.code.ssm.Cache}. Because using Spring Cache
 * and CacheManager it is not possible to pass expiration time to backend cache, default expiration time has to be set
 * in {@link SSMCache#SSMCache(com.google.code.ssm.Cache, int, boolean)}. This expiration time is used for all store
 * requests.
 * 
 * Methods {@link #addCache(SSMCache)} and {@link #removeCache(String)} aren't thread safe.
 * 
 * @author Jakub Białek
 * @since 3.0.0
 * 
 */
public class SSMCacheManager implements CacheManager, InitializingBean {

    private final ConcurrentMap<String, Cache> cacheMap = new ConcurrentHashMap<String, Cache>();

    private final Set<String> cacheNames = new LinkedHashSet<String>();
    
    @Getter
    private Collection<SSMCache> caches;
    
    @Override
    public void afterPropertiesSet() {
        Assert.notEmpty(caches, "A collection of caches is required and cannot be empty");
    }

    @Override
    public Collection<String> getCacheNames() {
        return Collections.unmodifiableSet(this.cacheNames);
    }

    @Override
    public SSMCache getCache(final String name) {
        SSMCache cache = (SSMCache) this.cacheMap.get(name);
        if (cache == null) {
            return null;
        }

        if (cache.getCache().getProperties().isUseNameAsKeyPrefix()) {
            return new SSMCache(new PrefixedCacheImpl(cache.getCache(), name, cache.getCache().getProperties().getKeyPrefixSeparator()),
                    cache.getExpiration(), cache.isAllowClear(), cache.isRegisterAliases(), cache.isMuteExceptions());
        }

        return cache;
    }

    /**
     * Adds new cache, registers also all aliases if set.
     * @since 4.1.0
     * 
     * @param cache
     */
    public void addCache(final SSMCache cache) {
        caches.add(cache);
        registerCache(cache);

    }
    
    /**
     * Removes given cache and related aliases.
     * 
     * @param name the name or alias of a cache
     * @since 4.1.0
     */
    public void removeCache(final String nameOrAlias) {
        final Cache cache = cacheMap.get(nameOrAlias);
        if (cache == null) {
            return;
        }
        
        final SSMCache ssmCache = (SSMCache) cache;
        if (ssmCache.isRegisterAliases()) {
            ssmCache.getCache().getAliases().forEach(this::unregisterCache);
        }
        
        unregisterCache(nameOrAlias);
        unregisterCache(cache.getName());
        caches.removeIf(c -> c.getName().equals(cache.getName()));
    }
    
    public void setCaches(Collection<SSMCache> caches) {
        Assert.notEmpty(caches, "A collection of caches cannot be empty or null");
        this.caches = caches;
        
        this.cacheMap.clear();
        this.cacheNames.clear();
        // preserve the initial order of the cache names
        caches.forEach(this::registerCache);
    }
    
    private void registerCache(final SSMCache cache) {
        registerCache(cache.getName(), cache);

        // use aliases if enabled
        if (cache.isRegisterAliases() && !CollectionUtils.isEmpty(cache.getCache().getAliases())) {
            for (String alias : cache.getCache().getAliases()) {
                registerCache(alias, cache);
            }
        }
    }
    
    private void registerCache(final String name, final Cache cache) {
        this.cacheMap.put(name, cache);
        this.cacheNames.add(name); 
    }
    
    private void unregisterCache(final String name) {
       this.cacheMap.remove(name);
       this.cacheNames.removeIf(n -> n.equals(name));
    }

}

@@ -1369,30 +1369,8 @@
 ter;
-%0Aimport lombok.Setter;
 %0A%0Aim

Remove unused import, #84