/**
 * 
 */
package com.quantgroup.asset.distribution.service.redis.impl;

import com.quantgroup.asset.distribution.service.redis.IRedisService;
import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.dao.DataAccessException;
import org.springframework.data.redis.connection.RedisConnection;
import org.springframework.data.redis.core.RedisCallback;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Service;
import redis.clients.jedis.Jedis;
import redis.clients.jedis.JedisCluster;
import redis.clients.jedis.JedisCommands;

import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.UUID;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;

/**
 *
 * @author renfeng
 * @date: 2017年3月30日 下午7:35:08
 * @version v1.0
 * @param <T>
 * @param <T>
 */
@Service
public class RedisServiceImpl<T> implements IRedisService<T> {

	private static final Logger logger = LoggerFactory.getLogger(RedisServiceImpl.class);
	
	@Autowired
	@Qualifier("redisTemplate")
	private RedisTemplate<String, T> redisTemplate;
	
	@Autowired
	@Qualifier("stringRedisTemplate")
	private StringRedisTemplate stringRedisTemplate;
	
	private ThreadLocal<String> lockFlag = new ThreadLocal<String>();
	
	public static final String UNLOCK_LUA;
	
	static {
        StringBuilder sb = new StringBuilder();
        sb.append("if redis.call(\"get\",KEYS[1]) == ARGV[1] ");
        sb.append("then ");
        sb.append("	   return redis.call(\"del\",KEYS[1]) ");
        sb.append("else ");
        sb.append("    return 0 ");
        sb.append("end ");
        UNLOCK_LUA = sb.toString();
    }
	
	@Override
	public void setString(String key, String value) {
		try{
			stringRedisTemplate.opsForValue().set(key, value);
		}catch(Exception e){
			logger.error("REDIS保存String异常!",e);
		}
	}

	@Override
	public void setStringEx(String key, String value, long time, TimeUnit timeUnit) {
		try{
			stringRedisTemplate.opsForValue().set(key, value, time, timeUnit);
		}catch(Exception e){
			logger.error("REDIS保存StringEx异常!",e);
		}
	}

	@Override
	public String getString(String key) {
		try{
			return stringRedisTemplate.opsForValue().get(key);
		}catch(Exception e){
			logger.error("REDIS查询String异常!",e);
		}
		return null;
	}

	@Override
	public void setEntity(String key, T entity) {
		try{
			redisTemplate.opsForValue().set(key, entity);
		}catch(Exception e){
			logger.error("REDIS保存Entity异常!",e);
		}
	}
	
	@Override
	public void setEntityEx(String key, T entity,long time, TimeUnit timeUnit) {
		try{
			redisTemplate.opsForValue().set(key, entity, time, timeUnit);
		}catch(Exception e){
			logger.error("REDIS保存EntityEx异常!",e);
		}
	}

	@Override
	public T getEntity(String key) {
        if(StringUtils.isEmpty(key)) return null;
        try{
			return (T)redisTemplate.opsForValue().get(key);
		}catch(Exception e){
			logger.error("REDIS查询Entity异常!",e);
		}
		return null;
	}

	@Override
	public void setList(String key, List<T> list) {
		try{
			redisTemplate.opsForList().leftPushAll(key, list);
		}catch(Exception e){
			logger.error("REDIS保存LIST异常!",e);
		}
	}
	
	@Override
	public void setListEx(String key, List<T> list,long time,TimeUnit timeUnit) {
		try{
			redisTemplate.opsForList().leftPushAll(key, list);
			redisTemplate.expire(key, time, timeUnit);
		}catch(Exception e){
			logger.error("REDIS保存LISTEX异常!",e);
		}
	}

	@Override
	public List<T> getList(String key) {
		try{
			List<T> range = redisTemplate.opsForList().range(key, 0, 10000l);
			if(range!=null && range.size()>0)
				return range.parallelStream().filter(t->t!=null).collect(Collectors.toList());
		}catch(Exception e){
			logger.error("REDIS查询LIST异常!",e);
		}
		return null;
	}

	@Override
	public boolean setIfAbsent(String key,String value,long time, TimeUnit timeUnit) {
		try{
			Boolean setIfAbsent = stringRedisTemplate.opsForValue().setIfAbsent(key, value);
			if(setIfAbsent)
				redisTemplate.expire(key, time, timeUnit);
			return setIfAbsent;
		}catch(Exception e){
			logger.error("REDIS原子性操作异常!",e);
		}
		return true;
	}

	@Override
	public long setIncr(String key, long value,long time,TimeUnit timeUnit) {
		try{
			Long increment = stringRedisTemplate.opsForValue().increment(key, value);
			if(value!=0)
				stringRedisTemplate.expire(key, time, timeUnit);
			return increment;
		}catch(Exception e){
			logger.error("REDIS原子自增操作异常!",e);
		}
		return 0l;
	}

	@Override
	public void del(String key) {
		try{
			redisTemplate.delete(key);
		}catch(Exception e){
			logger.error("REDIS删除操作异常!",e);
		}
	}

	@Override
	public void setMapEx(String key, Map<String,T> map, long time, TimeUnit timeUnit) {
		try{
			redisTemplate.opsForHash().putAll(key, map);
			redisTemplate.expire(key, time, timeUnit);
		}catch(Exception e){
			logger.error("REDIS保存Map操作异常!",e);
		}
	}

	@Override
	public Map<Object, Object> getMapEx(String key) {
		try{
			return redisTemplate.opsForHash().entries(key);
		}catch(Exception e){
			logger.error("REDIS保存Map操作异常!",e);
		}
		return null;
	}

	@Override
	public void hmset(String key, String hashKey, T value,long time,TimeUnit timeUnit) {
		try{
			redisTemplate.opsForHash().put(key, hashKey, value);
			redisTemplate.expire(key, time, timeUnit);
		}catch(Exception e){
			logger.error("REDIS hmset操作异常!",e);
		}
	}
	@SuppressWarnings("unchecked")
	@Override
	public T hmget(String key, String hashKey) {
		try{
			return (T) redisTemplate.opsForHash().get(key, hashKey);
		}catch(Exception e){
			logger.error("REDIS hmget操作异常!",e);
		}
		return null;
	}
	@Override
	public Long hmgetSize(String key) {
		try{
			return redisTemplate.opsForHash().size(key);
		}catch(Exception e){
			logger.error("REDIS hmgetSize操作异常!",e);
		}
		return null;
	}

	@Override
	public void rightPushEx(String key, T value,long time,TimeUnit timeUnit) {
		try{
			redisTemplate.opsForList().rightPush(key, value);
			redisTemplate.expire(key, time, timeUnit);
		}catch(Exception e){
			logger.error("REDIS rightPush操作异常!",e);
		}
	}

	@Override
	public void listRemove(String key, T value, int count) {
		try{
			redisTemplate.opsForList().remove(key, count, value);
		}catch(Exception e){
			logger.error("REDIS listRemove操作异常!",e);
		}
		
	}

	@Override
	public T leftPop(String key) {
		try{
			return redisTemplate.opsForList().leftPop(key);
		}catch(Exception e){
			logger.error("REDIS rightPop操作异常!",e);
		}
		return null;
	}
	
	@Override
	public long listSize(String key){
		try{
			return redisTemplate.opsForList().size(key);
		}catch(Exception e){
			logger.error("REDIS listSize操作异常!",e);
		}
		return 0;
	}

	@Override
	public boolean lock(String key, Long expireTime, Integer retryTimes, Long sleepMillis) {
		boolean result = setRedis(key, expireTime);
		while ((!result) && retryTimes-- > 0) {
			try {
				TimeUnit.MILLISECONDS.sleep(sleepMillis);
			} catch (InterruptedException e) {
				return false;
			}
			result = setRedis(key, expireTime);
		}
		return result;
	}

	@Override
	public boolean lock(String key, Long expireTime) {
		return lock(key, expireTime, 0, 0L);
	}

	@Override
	public boolean releaseLock(String key) {
		 try {
	            List<String> keys = new ArrayList<String>();
	            keys.add(key);
	            List<String> args = new ArrayList<String>();
	            args.add(lockFlag.get());
	            // 使用lua脚本删除redis中匹配value的key，可以避免由于方法执行时间过长而redis锁自动过期失效的时候误删其他线程的锁
	            // spring自带的执行脚本方法中，集群模式直接抛出不支持执行脚本的异常，所以只能拿到原redis的connection来执行脚本
	            Long result = redisTemplate.execute(new RedisCallback<Long>() {
	                public Long doInRedis(RedisConnection connection) throws DataAccessException {
	                    Object nativeConnection = connection.getNativeConnection();
	                    // 集群模式和单机模式虽然执行脚本的方法一样，但是没有共同的接口，所以只能分开执行
	                    // 集群模式
	                    if (nativeConnection instanceof JedisCluster) {
	                        return (Long) ((JedisCluster) nativeConnection).eval(UNLOCK_LUA, keys, args);
	                    }

	                    // 单机模式
	                    else if (nativeConnection instanceof Jedis) {
	                        return (Long) ((Jedis) nativeConnection).eval(UNLOCK_LUA, keys, args);
	                    }
	                    return 0L;
	                }
	            });

	            return result != null && result > 0;
	        } catch (Exception e) {
	            logger.error("release lock occured an exception", e);
	        }
	        return false;
	}

	private boolean setRedis(String key, long expireTime) {
		try {
			String result = redisTemplate.execute(new RedisCallback<String>() {
                @Override
                public String doInRedis(RedisConnection connection) throws DataAccessException {
                    JedisCommands commands = (JedisCommands) connection.getNativeConnection();
                    String uuid = UUID.randomUUID().toString();
                    lockFlag.set(uuid);
                    return commands.set(key, uuid, "NX", "PX", expireTime);
                }
            });
			return StringUtils.isNotEmpty(result);
		} catch (Exception e) {
			logger.error("设置redis分布式锁异常!", e);
			return false;
		}
	}
}
