package cn.freemud.utils;

import org.springframework.data.redis.core.RedisCallback;
import org.springframework.data.redis.core.RedisTemplate;

import java.util.Random;

public class RedisLock {

	/**
	 * 加锁标志
	 */
	public static final String LOCKED = "TRUE";
	/**
	 * 毫秒与毫微秒的换算单位 1毫秒 = 1000000毫微秒
	 */
	public static final long MILLI_NANO_CONVERSION = 1000 * 1000L;
	/**
	 * 默认超时时间（毫秒）
	 */
	public static final long DEFAULT_TIME_OUT = 5000;
	
	public static final Random RANDOM = new Random();
	/**
	 * 锁的超时时间（秒），过期删除
	 */
	public static final int EXPIRE = 60;

	private static RedisTemplate redisTemplate;
	
	private static RedisLock instance = new RedisLock();
	
	public static RedisLock getInstance(RedisTemplate redisTemplate){
		RedisLock.redisTemplate = redisTemplate;
		return instance;
	}

	public RedisLock() {
	}

	/**
	 * 加锁 应该以： lock(); try { doSomething(); } finally { unlock()； } 的方式调用
	 * 
	 * @param lockKey
	 * @param timeout
	 *            超时时间
	 * @return 成功或失败标志
	 */
	public boolean lock(String lockKey, long timeout) {
		return this.lock(lockKey, timeout, EXPIRE);
	}

	/**
	 * 加锁 应该以： lock(); try { doSomething(); } finally { unlock()； } 的方式调用
	 * 
	 * @param
	 * @param lockKey
     * @param timeout
	 *            超时时间(单位毫秒)
	 * @param expire
	 *            （秒），过期删除
	 * @return 成功或失败标志
	 */
	public boolean lock(String lockKey, long timeout, int expire) {
        long finalTimeOut = timeout * MILLI_NANO_CONVERSION;
        long nano = System.nanoTime();
		return (Boolean) redisTemplate.execute((RedisCallback) connection -> {
			try {
				while ((System.nanoTime() - nano) < finalTimeOut) {
					if (connection.setNX(lockKey.getBytes(), LOCKED.getBytes())) {
						connection.expire(lockKey.getBytes(), expire);
						return true;
					}
					Thread.sleep(3, RANDOM.nextInt(500));
				}
				long ttl = connection.ttl(lockKey.getBytes());
				if(ttl == -1) {
				    this.unlock(lockKey);
				    if(connection.setNX(lockKey.getBytes(),LOCKED.getBytes())) {
                        connection.expire(lockKey.getBytes(), expire);
                        return true;
                    } else {
				        return false;
                    }
                }
			} catch (Exception e) {
				throw new RuntimeException("Locking error", e);
			}
			return false;
		});
	}

	/**
	 * 加锁 应该以： lock(); try { doSomething(); } finally { unlock()； } 的方式调用
	 * 
	 * @return 成功或失败标志
	 */
	public boolean lock(String lockKey) {
		return lock(lockKey, DEFAULT_TIME_OUT);
	}

	/**
	 * 解锁 无论是否加锁成功，都需要调用unlock 应该以： lock(); try { doSomething(); } finally {
	 * unlock()； } 的方式调用
	 */
	public void unlock(String lockKey) {
		redisTemplate.delete(lockKey);
	}
}
