package com.ibeetl.admin.core.conf;

import java.io.UnsupportedEncodingException;
import java.util.concurrent.Callable;
import java.util.concurrent.ConcurrentHashMap;

import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.cache.Cache;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.cache.RedisCache;
import org.springframework.data.redis.cache.RedisCacheConfiguration;
import org.springframework.data.redis.cache.RedisCacheManager;
import org.springframework.data.redis.cache.RedisCacheWriter;
import org.springframework.data.redis.connection.Message;
import org.springframework.data.redis.connection.MessageListener;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.listener.PatternTopic;
import org.springframework.data.redis.listener.RedisMessageListenerContainer;
import org.springframework.data.redis.listener.adapter.MessageListenerAdapter;
import org.springframework.data.redis.serializer.JdkSerializationRedisSerializer;
import org.springframework.data.redis.serializer.RedisSerializationContext.SerializationPair;

/**
 * 支持一二级缓存，使得性能逆天快.默认不开启
 * 
 * @author xiandafu
 *
 */

//@Configuration
//@ConditionalOnProperty(name="springext.cache.enabled", havingValue="true" ,matchIfMissing=false)
public class CacheConfig {

	// 定义一个redis 的频道，默认叫cache，用于pub/sub
	@Value("${springext.cache.redis.topic:cache}")
	String topicName;

	@Bean
	public TowLevelCacheManager cacheManager(RedisTemplate redisTemplate) {

		RedisCacheWriter writer = RedisCacheWriter.lockingRedisCacheWriter(redisTemplate.getConnectionFactory());
		SerializationPair pair = SerializationPair
				.fromSerializer(new JdkSerializationRedisSerializer(this.getClass().getClassLoader()));
		RedisCacheConfiguration config = RedisCacheConfiguration.defaultCacheConfig().serializeValuesWith(pair);

		TowLevelCacheManager cacheManager = new TowLevelCacheManager(redisTemplate, writer, config);

		return cacheManager;
	}

	@Bean
	RedisMessageListenerContainer container(RedisConnectionFactory connectionFactory,
			MessageListenerAdapter listenerAdapter) {

		RedisMessageListenerContainer container = new RedisMessageListenerContainer();
		container.setConnectionFactory(connectionFactory);
		container.addMessageListener(listenerAdapter, new PatternTopic(topicName));
		return container;
	}

	@Bean
	MessageListenerAdapter listenerAdapter(final TowLevelCacheManager cacheManager) {
		return new MessageListenerAdapter(new MessageListener() {

			public void onMessage(Message message, byte[] pattern) {
				byte[] bs = message.getChannel();
				try {
					// Sub 一个消息，通知缓存管理器
					String type = new String(bs, "UTF-8");
					cacheManager.receiver(type);
				} catch (UnsupportedEncodingException e) {
					e.printStackTrace();
					// 不可能出错，忽略
				}

			}

		});
	}

	class TowLevelCacheManager extends RedisCacheManager {
		RedisTemplate redisTemplate;

		public TowLevelCacheManager(RedisTemplate redisTemplate, RedisCacheWriter cacheWriter,
				RedisCacheConfiguration defaultCacheConfiguration) {
			super(cacheWriter, defaultCacheConfiguration);
			this.redisTemplate = redisTemplate;
		}

		// 使用RedisAndLocalCache代替Spring Boot自带的RedisCache
		@Override
		protected Cache decorateCache(Cache cache) {
			return new RedisAndLocalCache(this, (RedisCache) cache);
		}

		// 通过其他分布式节点，缓存改变
		public void publishMessage(String cacheName) {
			this.redisTemplate.convertAndSend(topicName, cacheName);
		}

		// 接受一个消息清空本地缓存
		public void receiver(String name) {
			RedisAndLocalCache cache = ((RedisAndLocalCache) this.getCache(name));
			if (cache != null) {
				cache.clearLocal();
			}
		}

	}

	class RedisAndLocalCache implements Cache {
		// 本地缓存提供
		ConcurrentHashMap<Object, Object> local = new ConcurrentHashMap<Object, Object>();
		RedisCache redisCache;
		TowLevelCacheManager cacheManager;

		public RedisAndLocalCache(TowLevelCacheManager cacheManager, RedisCache redisCache) {
			this.redisCache = redisCache;
			this.cacheManager = cacheManager;
		}

		@Override
		public String getName() {
			return redisCache.getName();
		}

		@Override
		public Object getNativeCache() {
			return redisCache.getNativeCache();
		}

		@Override
		public ValueWrapper get(Object key) {
			ValueWrapper wrapper = (ValueWrapper) local.get(key);
			if (wrapper != null) {
				return wrapper;
			} else {
				// 二级缓存取
				wrapper = redisCache.get(key);
				if (wrapper != null) {
					local.put(key, wrapper);
				}

				return wrapper;
			}

		}

		@Override
		public <T> T get(Object key, Class<T> type) {

			return redisCache.get(key, type);
		}

		@Override
		public <T> T get(Object key, Callable<T> valueLoader) {
			return redisCache.get(key, valueLoader);
		}

		@Override
		public void put(Object key, Object value) {
			System.out.println(value.getClass().getClassLoader());
			redisCache.put(key, value);
			clearOtherJVM();

		}

		@Override
		public ValueWrapper putIfAbsent(Object key, Object value) {
			ValueWrapper v = redisCache.putIfAbsent(key, value);
			clearOtherJVM();
			return v;

		}

		@Override
		public void evict(Object key) {
			redisCache.evict(key);
			clearOtherJVM();

		}

		@Override
		public void clear() {
			redisCache.clear();

		}

		public void clearLocal() {
			this.local.clear();
		}

		protected void clearOtherJVM() {
			cacheManager.publishMessage(redisCache.getName());
		}

	}

}
