package cn.myapps.util.cache;

import java.time.Duration;
import java.util.Collection;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Set;
import java.util.concurrent.*;

import cn.myapps.base.web.WebUser;
import cn.myapps.common.util.PropertyUtil;
import cn.myapps.common.util.cache.*;
import com.github.benmanes.caffeine.cache.Caffeine;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.cache.Cache;
import org.springframework.cache.CacheManager;
import org.springframework.data.redis.core.StringRedisTemplate;

import cn.myapps.common.util.SpringApplicationContextUtil;

import static java.util.concurrent.TimeUnit.SECONDS;

public class RedisProvider implements ICacheProvider {
	CacheManager manager = null;// CacheManager.create();

	StringRedisTemplate redisTemplate;

	public RedisProvider() {
		manager = SpringApplicationContextUtil.getBean(CacheManager.class);
		redisTemplate =  SpringApplicationContextUtil.getBean(StringRedisTemplate.class);
	}

	HashMap<String, MethodCacheCleaner> clearedNames = new HashMap<String, MethodCacheCleaner>();

	public IMyCache createCache(java.lang.String name, int maxElementsInMemory, boolean overflowToDisk, boolean eternal,
			long timeToLiveSeconds, long timeToIdleSeconds) {

		Cache cache = manager.getCache(name);
		MyCache mycache = new MyCache(cache);
		return mycache;
	}

	/**
	 * 获取默认缓存对象
	 * 
	 * @return 返回默认缓存对象
	 */
	public IMyCache getDefaultCache() {
		return getCache(ICacheProvider.DEFAULT_CACHE_NAME);
	}

	/**
	 * 根据缓存名称获取缓存对象
	 * 
	 * @return 返回缓存对象
	 */
	public IMyCache getCache(String name) {
		Cache c = manager.getCache(name);
		if (c != null) {
			MyCache mycache = new MyCache(c);
			return mycache;
		} else {
			return null;
		}
	}

	/**
	 * 根据缓存名称清除缓存
	 */
	public void clearCache(String name) {
		Cache cache = manager.getCache(name);
		if (cache != null) {
			cache.clear();
		}
	}

	/**
	 * 获取所有需要清除缓存的方法名称
	 * 
	 * @return 所有名称
	 */
	public String[] getCacheNames() {
		Collection<String> names = manager.getCacheNames();
		String[] rtns = new String[names.size()];
		int i = 0;
		for (Iterator iterator = names.iterator(); iterator.hasNext();) {
			String name = (String) iterator.next();
			rtns[i] = name;
			i++;
		}
		return rtns;
	}

	/**
	 * 获取所有需要清除缓存的方法名称
	 * 
	 * @return 所有名称
	 */
	public String[] getClearedNames() {
		return clearedNames.keySet().toArray(new String[clearedNames.keySet().size()]);
	}

	/**
	 * 清除所有缓存
	 */
	public void clearAll() {
		// Environment.cleanPermissionMap();
		String[] cacheNames = getCacheNames();
		for (int i = 0; i < cacheNames.length; i++) {
			String name = cacheNames[i];
			clearCache(cacheNames[i]);
		}
	}

	public void remove(String key) {
		redisTemplate.delete(key);
	}

	/**
	 * 根据cacheName获取keySet
	 * @param cacheName
	 * @return
	 */
	public Set getKeys(String cacheName){
		Set keys = redisTemplate.keys(cacheName + "*");
		return keys;
	}

	/**
	 * 根据缓存方法名称清除缓存
	 * 
	 * @return true|false:true清除成功，false清除失败
	 */
	public boolean clearByCacheName(String cacheName) {
		MethodCacheCleaner cleaner = clearedNames.get(cacheName);
		if (cleaner != null && cleaner.isClearAll()) {
			clearAll();
			return true;
		}
		return false;
	}

	/**
	 * 设置清除掉的缓存键值对
	 */
	public void setClearedNames(HashMap<String, MethodCacheCleaner> clearedNames) {
		this.clearedNames = clearedNames;
	}

	static class MyCache implements IMyCache {
		private static final Logger logger = LoggerFactory.getLogger(MyCache.class);
		private static final boolean IS_NETWORK = PropertyUtil.IsNetworkEnvironment();
		private static final int EXPIRED_IN_SECONDS = 10*60;
		private static final CopyOnWriteArrayList<Object> _removedKeys = new CopyOnWriteArrayList();
		private static final com.github.benmanes.caffeine.cache.Cache<Object, Object> _caffineLocalCache;

		private static ICacheProvider cacheProvider;

		static {
			/**
			 * 针对内网,每10秒执行一次
			 */
			if (!IS_NETWORK) {
				_caffineLocalCache = Caffeine.newBuilder()
						.maximumSize(2000)
						.expireAfterWrite(EXPIRED_IN_SECONDS, SECONDS)
						.evictionListener((key, value, cause) -> {
							if (value instanceof WebUser) {
								_removedKeys.add(key);
							}
						})
						.softValues()
						.build();

				cacheProvider = MyCacheManager.getProviderInstance(WebUser.CACHE_PROVIDER);
			} else {
				_caffineLocalCache = Caffeine.newBuilder()
						.maximumSize(2000)
						.softValues()
						.build();
			}

			Executors.newScheduledThreadPool(1)
					.scheduleAtFixedRate(() -> {
						Object[] keys = _removedKeys.toArray();
						if(keys!=null) {
							IMyCache iMyCache = cacheProvider.getCache(WebUser.CACHE_KEY_WEBUSER);
							if(iMyCache!=null) {
								for (int index = 0; index < keys.length; index++) {
									Object selectedKey = keys[index];
									try {
										if (selectedKey != null) {
											iMyCache.remove(selectedKey);
											_removedKeys.remove(selectedKey);
										}
									} catch (Exception ex) {
										logger.error("定时清除数据:{}", selectedKey);
									}
								}
							}
						}
					}, 1, EXPIRED_IN_SECONDS, SECONDS);
		}

		Cache cache;

		MyCache(Cache cache) {
			this.cache = cache;
		}

		public IMyElement get(Object key) {
			if (key == null) {
				return null;
			}

			try {
				Object  value = _caffineLocalCache.getIfPresent(key);
				if (value == null) {
					Cache.ValueWrapper element = cache.get(key);

					if (element == null) {
						return null;
					} else if(element.get() instanceof WebUser) {
						_caffineLocalCache.put(key, element.get());
					}

					return new MyElement(key, element.get());
				}

				return new MyElement(key,value);
			} catch (Exception ex) {
				ex.printStackTrace();
				return null;
			}
		}

		public void put(IMyElement element) {
			if (element.getValue() instanceof WebUser) {
				try {
					_caffineLocalCache.put(element.getKey(), element.getValue());
				} catch (Exception ex) {
					//todo: 忽略少量异常的情况
					if (element.getKey() != null) {
						_caffineLocalCache.invalidate(element.getKey());
					}
				}
			}


			cache.put(element.getKey(), element.getValue());
		}

		public void put(Object key, Object value) {
			MyElement element = new MyElement(key, value);
			put(element);
		}

		@Override
		public void remove(Object key) {
			_caffineLocalCache.invalidate(key);
			cache.evict(key);
		}

		@Override
		public void clear() {
			_caffineLocalCache.invalidateAll();
			cache.clear();
		}
	}

	static class MyElement implements IMyElement {
		Object key;
		Object value;

		MyElement(Object key, Object value) {
			this.key = key;
			this.value = value;
		}

		public Object getValue() {
			return this.value;
		}

		public Object getKey() {
			return this.key;
		}

	}
}
