package com.yxcheng.token.common.cache;


import com.yxcheng.token.utils.SerializeUtil;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.dao.DataAccessException;
import org.springframework.data.redis.connection.RedisConnection;
import org.springframework.data.redis.connection.jedis.JedisConnectionFactory;
import org.springframework.data.redis.core.RedisCallback;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.serializer.Jackson2JsonRedisSerializer;
import org.springframework.data.redis.serializer.StringRedisSerializer;
import org.springframework.stereotype.Service;
import org.springframework.util.StringUtils;
import redis.clients.jedis.JedisPoolConfig;

import java.io.UnsupportedEncodingException;
import java.util.*;

@SuppressWarnings({ "unchecked", "rawtypes" })
@Service
public class RedisService {

	private Logger LOG = LoggerFactory.getLogger(RedisService.class);

	@Autowired
	private List<RedisTemplate<String, String>> redisTemplateList;
	
	@Autowired
	private RedisTemplate<String, String> redisTemplate;

	/**
	 * 从缓存中删除指定的key
	 * @param keys
	 */
	public void del(final String... keys) {
		for (RedisTemplate<String, String> redisTemplate : redisTemplateList) {
			try {
				redisTemplate.execute(new RedisCallback<Long>() {
					public Long doInRedis(RedisConnection connection) throws DataAccessException {
						long result = 0;
						for (int i = 0; i < keys.length; i++) {
							result = connection.del(keys[i].getBytes());
						}
						return result;
					}
				});

			} catch (Exception e) {
				e.printStackTrace();
			}
		}
	}

	/**
	 * 重缓存中删除指定的key 模式匹配，效率低
	 * @param keys
	 */
	public void delByReg(final String... keys) {
		for (RedisTemplate<String, String> redisTemplate : redisTemplateList) {
			try {
				redisTemplate.execute(new RedisCallback<Long>() {
					public Long doInRedis(RedisConnection connection) throws DataAccessException {
						long result = 0;
						for (int i = 0; i < keys.length; i++) {
							Set<byte[]> keyset = connection.keys((keys[i] + "*").getBytes());
							for (byte[] key : keyset) {
								result = connection.del(key);
							}
						}
						return result;
					}
				});

			} catch (Exception e) {
				e.printStackTrace();
			}
		}
	}

	/**
	 * 判断一个键是否存在于缓存中
	 * @param key
	 * @return
	 */
	public boolean exists(final String key) {
		Boolean result = false;
		for (RedisTemplate<String, String> redisTemplate : redisTemplateList) {
			try {
				result = (Boolean) redisTemplate.execute(new RedisCallback<Object>() {
					public Boolean doInRedis(RedisConnection connection) throws DataAccessException {
						return connection.exists(key.getBytes());
					}
				});

				if (result) {
					break;
				}
			} catch (Exception e) {
				e.printStackTrace();
				LOG.info(key + "=" + e.getMessage());
			}
		}

		return result;
	}

	/**
	 * 向缓存中插入数据
	 * @param key
	 * @param value
	 * @param liveTime
	 */
	public void set(final byte[] key, final byte[] value, final long liveTime) {
		for (RedisTemplate<String, String> redisTemplate : redisTemplateList) {
			try {
				redisTemplate.execute(new RedisCallback<Object>() {
					public Long doInRedis(final RedisConnection connection) throws DataAccessException {
						connection.set(key, value);
						if (liveTime > 0) {
							connection.expire(key, liveTime);
						}
						return 1L;
					}
				});
			} catch (Exception e) {
				e.printStackTrace();
			}
		}
	}

    public <T> void set(String key, T value, Class<T> type) {
        if (value == null)
            return;
        try {
			Jackson2JsonRedisSerializer<T> serializer = new Jackson2JsonRedisSerializer<>(type);
            byte[] _value = serializer.serialize(value);
            this.set(key.getBytes(), _value, 0);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }


    public <T> void set(String key, T value, long liveTime, Class<T> type) {
		if (value == null)
			return;
		try {
			Jackson2JsonRedisSerializer<T> serializer = new Jackson2JsonRedisSerializer<T>(type);
			byte[] _value = serializer.serialize(value);
			this.set(key.getBytes(), _value, liveTime);
		} catch (Exception e) {
			e.printStackTrace();
		}
	}

	public void set(String key, String value, long liveTime) {
		this.set(key.getBytes(), value.getBytes(), liveTime);
	}
	
	public void set(String key, String value) {
		this.set(key.getBytes(), value.getBytes(), 0);
	}

	public void set(String key, String value, String charSet, long liveTime) {
		if(null == value) {
			return;
		}
		try {
			this.set(key.getBytes(), value.getBytes(charSet), liveTime);
		} catch (UnsupportedEncodingException e) {
			e.printStackTrace();
		}
	}

    public void set(String key, String value, String charSet) {
        if(null == value) {
            return;
        }
        try {
            this.set(key.getBytes(), value.getBytes(charSet), 0);
        } catch (UnsupportedEncodingException e) {
            e.printStackTrace();
        }
    }
	
	public <T> List<T> getList(final String key, final Class<T> type) {
		return this.lRange(key, 0L, -1L, type);
	}

	public<T> List<T> getList(String key) {
		byte[] data = this.getBytes(key);
		if(data == null){
			return null;
		}
		List<T>list = (List<T>) SerializeUtil.unserialize(data);
		return list;
	}

	public <T>void setList(final String key, final List<T> list) {

		this.set(key.getBytes(), SerializeUtil.serialize(list),0L);
	}
	
	public <T>void setList(final String key, final List<T> list,long seconds) {
		this.set(key.getBytes(), SerializeUtil.serialize(list),seconds);
	}
	
	public <T> List<T> lRange(final String key, final Long begin, final Long end, final Class<T> type) {
		return redisTemplate.execute(new RedisCallback<List<T>>() {

			public List<T> doInRedis(RedisConnection connection) throws DataAccessException {
				Jackson2JsonRedisSerializer<T> serializer = new Jackson2JsonRedisSerializer<T>(type);
				List<T> list = new ArrayList<T>();
				for (byte[] element : connection.lRange(key.getBytes(), begin, end)) {
					T bean = serializer.deserialize(element);
					list.add(bean);
				}
				return list;
			}
		});
	}

	/**
	 * 从缓存中获取数据
	 * @param key
	 * @return
	 */
	public String get(final String key) {
		String cacheValue = "";
		for (RedisTemplate<String, String> redisTemplate : redisTemplateList) {
			try {
				cacheValue = redisTemplate.execute(new RedisCallback<String>() {
					public String doInRedis(RedisConnection connection) throws DataAccessException {
						try {
							byte[] cacheBytes = connection.get(key.getBytes());
							if (cacheBytes != null) {
								String cacheStr = new String(cacheBytes, "utf-8");
								return cacheStr;
							}
						} catch (Exception e) {
							LOG.info(e.getMessage());
						}
						return "";
					}
				});
				if (!StringUtils.isEmpty(cacheValue))
					break;
			} catch (Exception e) {
				e.printStackTrace();
			}
		}
		return cacheValue;
	}


	public byte[] getBytes(final String key) {
		byte[] cacheValue = null;
		for (RedisTemplate<String, String> redisTemplate : redisTemplateList) {
			try {
				cacheValue = redisTemplate.execute(new RedisCallback<byte[]>() {
					public byte[] doInRedis(RedisConnection connection) throws DataAccessException {
						try {
							byte[] cacheBytes = connection.get(key.getBytes());
							if (cacheBytes != null) {
								return cacheBytes;
							}
						} catch (Exception e) {
							LOG.info(e.getMessage());
						}
						return null;
					}
				});
				if (cacheValue!=null)
					break;
			} catch (Exception e) {
				e.printStackTrace();
			}
		}
		return cacheValue;
	}

	public <T> T get(final String key, final Class<T> clazz) {
		T t = null;
		for (RedisTemplate<String, String> redisTemplate : redisTemplateList) {
			t = redisTemplate.execute(new RedisCallback<T>() {
				public T doInRedis(RedisConnection connection) throws DataAccessException {
					byte[] data = connection.get(key.getBytes());
					if (data != null)
						return new Jackson2JsonRedisSerializer<T>(clazz).deserialize(data);
					return null;
				}
			});

			if (t != null)
				break;
		}
		return t;
	}
	
	public <T> Long rPush(final String key, final List<T> list, final Class<T> type, final long expire) {
		if (list == null) {
			return 0L;
		}
		return redisTemplate.execute(new RedisCallback<Long>() {

			public Long doInRedis(RedisConnection connection) throws DataAccessException {
				Jackson2JsonRedisSerializer<T> serializer = new Jackson2JsonRedisSerializer<T>(type);
				connection.multi();
				for (T value : list) {
					connection.rPush(key.getBytes(), serializer.serialize(value));
				}
				if (expire > 0) {
					connection.expire(key.getBytes(), expire);
				}
				connection.exec();
				return new Long(list.size());
			}

		});
	}

	/**
	 * 清空缓存
	 */
	public void flushDB() {
		for (RedisTemplate<String, String> redisTemplate : redisTemplateList) {
			try {
				redisTemplate.execute(new RedisCallback<String>() {
					public String doInRedis(RedisConnection connection) throws DataAccessException {
						connection.flushDb();
						return "ok";
					}
				});
			} catch (Exception e) {
				e.printStackTrace();
			}
		}
	}

	/**
	 * 添加至有序集合
	 * @param key
	 * @param score
	 * @param value
	 */
	public void zadd(final byte[] key, final double score, final byte[] value) {
		for (RedisTemplate<String, String> redisTemplate : redisTemplateList) {
			try {
				redisTemplate.execute(new RedisCallback<String>() {
					public String doInRedis(RedisConnection connection) throws DataAccessException {
						connection.zAdd(key, score, value);
						return "ok";
					}
				});
			} catch (Exception e) {
				e.printStackTrace();
			}
		}
	}

	/**
	 * 按条件获取有序集合元素子集
	 * @param key
	 *            有序集合key
	 * @param min
	 *            范围最小值
	 * @param max
	 *            范围最大值
	 * @param offset
	 *            从第0ffset+1个元素起
	 * @param count
	 *            返回上限
	 * @return
	 */
	public Set<byte[]> zRangeByScore(final byte[] key, final double min, final double max, final long offset,
			final long count) {
		Set<byte[]> set = null;
		for (RedisTemplate<String, String> redisTemplate : redisTemplateList) {
			try {
				set = redisTemplate.execute(new RedisCallback<Set<byte[]>>() {

					public Set<byte[]> doInRedis(RedisConnection connection) throws DataAccessException {
						return connection.zRangeByScore(key, min, max, offset, count);
					}

				});
				if (set != null) {
					break;
				}
			} catch (Exception e) {
				e.printStackTrace();
			}
		}
		return set;
	}

	/**
	 * 添加指定map至缓存
	 * @param key map唯一标识
	 * @param hashes
	 */
	public void hMSet(final byte[] key, final Map<byte[], byte[]> hashes, final long liveTime) {
		for (RedisTemplate<String, String> redisTemplate : redisTemplateList) {
			try {
				redisTemplate.execute(new RedisCallback() {
					public Object doInRedis(RedisConnection connection) throws DataAccessException {
						connection.hMSet(key, hashes);
						if (liveTime > 0) {
							connection.expire(key, liveTime);
						}
						return null;
					}
				});

			} catch (Exception e) {
				e.printStackTrace();
			}
		}
	}
	
	/**
	* @describe: 操作hashMap
	* @author：caoxiaohong
	* @createtime：2016年9月28日 下午5:34:06
	* @param key
	* @param hashes
	* @return void
	*/
	public void hMSet(final String key, final Map<String, String> hashes) {
		StringRedisSerializer serializer = new StringRedisSerializer();
       Map<byte[], byte[]> byMap = new HashMap<byte[], byte[]>();
       for(String hashkey:hashes.keySet()) {
    	   byMap.put(serializer.serialize(hashkey), serializer.serialize(hashes.get(hashkey)));
       }
       hMSet(serializer.serialize(key), byMap, 0);
	}

	/**
	 * 设置生命周期
	 * @param key
	 * @param hashes
	 * @param liveTime
	 */
	public void hMSet(final String key, final Map<String, String> hashes, final long liveTime) {
		StringRedisSerializer serializer = new StringRedisSerializer();
		Map<byte[], byte[]> byMap = new HashMap<byte[], byte[]>();
		for(String hashkey:hashes.keySet()) {
			byMap.put(serializer.serialize(hashkey), serializer.serialize(hashes.get(hashkey)));
		}
		hMSet(serializer.serialize(key), byMap, liveTime);
	}

	/**
	 * 获取指定map中指定键对应的值列表
	 * @param key map的唯一标识
	 * @param fields 键数组
	 * @return 值数组
	 */
	public List<byte[]> hMGet(final byte[] key, final byte[]... fields) {
		List<byte[]> cacheValue = null;
		for (RedisTemplate<String, String> redisTemplate : redisTemplateList) {
			try {
				cacheValue = redisTemplate.execute(new RedisCallback<List<byte[]>>() {
					public List<byte[]> doInRedis(RedisConnection connection) throws DataAccessException {
						return connection.hMGet(key, fields);
					}
				});
				if (cacheValue != null) {
					break;
				}
			} catch (Exception e) {
				e.printStackTrace();
			}
		}
		return cacheValue;
	}
	
	public void hMSet(final byte[] key, final byte[] field, final byte[] value, final long liveTime) {
		for (RedisTemplate<String, String> redisTemplate : redisTemplateList) {
			try {
				redisTemplate.execute(new RedisCallback() {
					public Object doInRedis(RedisConnection connection) throws DataAccessException {
						connection.hSet(key, field, value);
						if (liveTime > 0) {
							connection.expire(key, liveTime);
						}
						return null;
					}
				});

			} catch (Exception e) {
				e.printStackTrace();
			}
		}
	}
	
	/**
	* @describe: 设置hash存储单个属性
	* @author：caoxiaohong
	* @createtime：2016年9月29日 下午12:03:53
	* @param key
	* @param field
	* @param value
	* @return void
	*/
	public void hMSet(String key, String field, String value) {
		StringRedisSerializer serializer = new StringRedisSerializer();
		hMSet(serializer.serialize(key), serializer.serialize(field), serializer.serialize(value), 0);
	}
	
	
	public byte[] hMGet(final byte[] key, final byte[] field) {
		byte[] cacheValue = null;
		for (RedisTemplate<String, String> redisTemplate : redisTemplateList) {
			try {
				cacheValue = redisTemplate.execute(new RedisCallback<byte[]>() {
					public byte[] doInRedis(RedisConnection connection) throws DataAccessException {
						return connection.hGet(key, field);
					}
				});
				if (cacheValue != null) {
					break;
				}
			} catch (Exception e) {
				e.printStackTrace();
			}
		}
		return cacheValue;
	}
	
	/**
	* @describe: 获取单个hash字段
	* @author：caoxiaohong
	* @createtime：2016年9月29日 下午3:51:14
	* @param key
	* @param field
	* @return
	* @return String
	*/
	public String hMGet(String key, String field) {
		String value = "";
		StringRedisSerializer serializer = new StringRedisSerializer();
		if(null != hMGet(serializer.serialize(key), serializer.serialize(field))) {
			value = serializer.deserialize(hMGet(serializer.serialize(key), serializer.serialize(field)));
		}
		return value;
	}
	
	
	/**
	* @describe: 检测hash中属性值是否存在
	* @author：caoxiaohong
	* @createtime：2016年9月29日 下午3:56:21
	* @param key
	* @param field
	* @return
	* @return boolean
	*/
	public boolean hExists(final String key, final String field) {
		Boolean result = false;
		for (RedisTemplate<String, String> redisTemplate : redisTemplateList) {
			try {
				result = (Boolean) redisTemplate.execute(new RedisCallback<Object>() {
					public Boolean doInRedis(RedisConnection connection) throws DataAccessException {
						return connection.hExists(key.getBytes(), field.getBytes());
					}
				});

				if (result) {
					break;
				}
			} catch (Exception e) {
				e.printStackTrace();
			}
		}

		return result;
	}
	
	
	/**
	* @describe: 设置hash增加值
	* @author：caoxiaohong
	* @createtime：2016年9月29日 下午4:26:58
	* @param key
	* @param field
	* @param delta
	* @return
	* @return Long
	*/
	public Long hIncrBy(final String key, final String field, final long delta) {
		Long result = null;
		for (RedisTemplate<String, String> redisTemplate : redisTemplateList) {
			try {
				result = (Long) redisTemplate.execute(new RedisCallback<Object>() {
					public Long doInRedis(RedisConnection connection) throws DataAccessException {
						return connection.hIncrBy(key.getBytes(), field.getBytes(), delta);
					}
				});
				if (result != null) {
					break;
				}
			} catch (Exception e) {
				e.printStackTrace();
			}
		}

		return result;
	}
	
	
	public Map<byte[], byte[]> hGetAll(final byte[] key) {
		Map<byte[], byte[]> cacheValue = null;
		for (RedisTemplate<String, String> redisTemplate : redisTemplateList) {
			try {
				cacheValue = redisTemplate.execute(new RedisCallback<Map<byte[], byte[]>>() {
					public Map<byte[], byte[]> doInRedis(RedisConnection connection) throws DataAccessException {
						return connection.hGetAll(key);
					}
				});
				if (cacheValue != null) {
					break;
				}
			} catch (Exception e) {
				e.printStackTrace();
			}
		}
		return cacheValue;
	}
	
	/**
	* @describe: 获取hash下所有值
	* @author：caoxiaohong
	* @createtime：2016年9月29日 下午5:08:03
	* @param key
	* @return
	* @return Map<String,String>
	*/
	public Map<String, String> hGetAll(final String key) {
		StringRedisSerializer serializer = new StringRedisSerializer();
		Map<String, String> allMap = new HashMap<String, String>();
		Map<byte[], byte[]> byMap = hGetAll(key.getBytes());
		for(byte[] by:byMap.keySet()) {
			allMap.put(serializer.deserialize(by), serializer.deserialize(byMap.get(by)));
		}
		return allMap;
	}


	// getters and setters
	public List<RedisTemplate<String, String>> getRedisTemplateList() {
		return redisTemplateList;
	}

	public void setRedisTemplateList(List<RedisTemplate<String, String>> redisTemplateList) {
		this.redisTemplateList = redisTemplateList;
	}


/*
	public Map<Object,Object> scan(String key) {
		return redisTemplate.execute((RedisCallback<Map<Object,Object>>) connection -> {
			Cursor<Map.Entry<Object,Object>> cursor = null;
			try {
				Map<Object,Object> data = new HashMap<>();
				redisTemplate.opsForHash().scan("field",
						ScanOptions.scanOptions().match(key + "*").count(1000).build());
				while (cursor.hasNext()) {
					Object key1 = cursor.next().getKey();
					Object value = cursor.next().getValue();
					data.put(key1,value);
				}
				//关闭cursor
				return data;
			}catch (Exception e){
				e.printStackTrace();
			}finally {
				try {
					cursor.close();
				} catch (IOException e) {
					e.printStackTrace();
				}
			}
			return null;
		});
	}
*/


	public Set<String> keys(String key){
		Set<String>set = new HashSet<>();
		for (RedisTemplate<String, String> redisTemplate : redisTemplateList) {

			/*Set<String> cacheValue = redisTemplate.execute(new RedisCallback<Set<String>>() {
				public Set<String> doInRedis(RedisConnection connection) throws DataAccessException {
					return connection.keys((key +"*").getBytes());
				}
			});
			if (cacheValue != null) {
				break;
			}*/

			Set<String> keys = redisTemplate.keys(key + "*");
			set.addAll(keys);
		}
		return set;
	}

	public Long incr(final byte[] key, final long liveTime) {
		Long result = null;
		for (RedisTemplate<String, String> redisTemplate : redisTemplateList) {
			try {
				result = (Long) redisTemplate.execute(new RedisCallback<Object>() {
					public Long doInRedis(RedisConnection connection) throws DataAccessException {
						Long incr= connection.incr(key);
						if(liveTime > 0) {
							connection.expire(key, liveTime);
						}
						return incr;
					}
				});
				if (result != null) {
					break;
				}
			} catch (Exception e) {
				e.printStackTrace();
			}
		}

		return result;
	}

	public Long incr(String key, long liveTime) {
		return this.incr(key.getBytes(), liveTime);
	}



	public RedisTemplate<String, String> getRedisTemplate() {
		return redisTemplate;
	}

	public String getKey(final String key) throws Exception {
		String cacheValue = "";
		for (RedisTemplate<String, String> redisTemplate : redisTemplateList) {
            cacheValue = redisTemplate.execute(new RedisCallback<String>() {
                public String doInRedis(RedisConnection connection) throws DataAccessException {
                    try {
                        byte[] cacheBytes = connection.get(key.getBytes());
                        if (cacheBytes != null) {
                            String cacheStr = new String(cacheBytes, "utf-8");
                            return cacheStr;
                        }
                    } catch (Exception e) {
                        LOG.info(e.getMessage());
                    }
                    return "";
                }
            });
		}
		return cacheValue;
	}


	public static void main(String[] args) {

		JedisPoolConfig jedisPoolConfig = new JedisPoolConfig();

		JedisConnectionFactory cf = new JedisConnectionFactory(jedisPoolConfig);
		cf.setHostName("monitor.taxservices.cn"); // -- @Deprecated
		cf.setPort(6365); // -- @Deprecated
		cf.setPassword("nikanba888"); // -- @Deprecated
		cf.setTimeout(30000); // -- @Deprecated 貌似没生效，30s超时，没有关闭连接池的连接；
		// --redis没有设置超时，会一直ESTABLISHED；redis设置了超时，且超时之后，会一直CLOSE_WAIT
		cf.afterPropertiesSet();



		RedisTemplate template = new RedisTemplate();
		template.setConnectionFactory(cf);
		String t = (String) template.execute(new RedisCallback<String>() {
			public String doInRedis(RedisConnection connection) throws DataAccessException {
				try {
					byte[] cacheBytes = connection.get("a".getBytes());
					if (cacheBytes != null) {
						String cacheStr = new String(cacheBytes, "utf-8");
						return cacheStr;
					}
				} catch (Exception e) {
				}
				return "";
			}
		});
//		Set keys = template.keys("abc*");
		System.out.println(t);

	}
}
