/**
 * Copyright 2008-2009. Chongqing Communications Industry Services Co.,Ltd Information Technology Branch. All rights reserved. <a>http://www.cqcis.com</a>
 */
package com.crunii.micro.component.redis.lock;

import com.crunii.micro.common.exception.MicroException;
import com.crunii.micro.common.utils.LogbackUtil;
import com.crunii.micro.common.utils.RandomUtil;
import com.crunii.micro.common.utils.Slf4jUtil;
import lombok.extern.slf4j.Slf4j;
import org.redisson.Redisson;
import org.redisson.api.RLock;
import org.redisson.api.RedissonClient;
import org.redisson.config.Config;
import org.springframework.core.NamedThreadLocal;
import org.springframework.util.Assert;
import org.springframework.util.StringUtils;

import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;

/**
 * @author 田平 create 2016年4月22日下午2:50:22
 */
@Slf4j
public class DistributeLock {
	/**
	 * 毫秒
	 */
	private static final int MIN_TIMEOUT = 5000;
	/**
	 * 毫秒
	 */
	private static final int MAX_TIMEOUT = 30000;

	/**
	 * 2分钟
	 */
	private static final int DEFAULT_EXPIRE_TIME = 2 * 60;

	/**
	 * 默认为lock
	 */
	private String namespace = "lock";

	/**
	 * 测试使用无意义
	 */
	private int testSum = 0;

	private RedissonClient redisson;

	private ThreadLocal<String> threadLocalLockId = new NamedThreadLocal<>("DistributeLock");

	public DistributeLock(RedissonClient redisson) {
		this.redisson = redisson;
	}

	public void lock(String id) {
		lock(id, DEFAULT_EXPIRE_TIME);
	}

	/**
	 * @param id
	 * @param expiredTime 单位秒
	 */
	public void lock(String id, int expiredTime) {
		Assert.hasText(id, "lockId not allowed null!");
		String lockId = this.namespace + ":" + id;
		//后面单位是毫秒所以乘1000
		int leaseTime = expiredTime * 1000;
		try {
			String currLockId = threadLocalLockId.get();
			if (lockId.equals(currLockId)) {
				throw new MicroException(MicroException.LOCK_FAILURE, lockId, "current thread already locked this lockId!");
			} else {
				RLock rlock = redisson.getLock(lockId);
				//随机等待时间，防止死锁出现
				long waitTimeout = RandomUtil.getRandom(MIN_TIMEOUT, MAX_TIMEOUT);
				boolean lockResult = rlock.tryLock(waitTimeout, leaseTime, TimeUnit.MILLISECONDS);
				if (lockResult) {
					threadLocalLockId.set(lockId);
					log.info("lockId={} lock success!", lockId);
				} else {
					String errorMsg = Slf4jUtil.formatMsg("lock wait timeout!{} ms", waitTimeout);
					throw new MicroException(MicroException.LOCK_FAILURE, lockId, errorMsg);
				}
			}
		} catch (Exception e) {
			throw new MicroException(MicroException.LOCK_FAILURE, e, lockId);
		}
	}

	public void unlock() {
		String currLockId = threadLocalLockId.get();
		if (StringUtils.hasText(currLockId)) {
			RLock rlock = redisson.getLock(currLockId);
			if (rlock != null) {
				threadLocalLockId.remove();
				rlock.unlock();
				log.info("lockId={} unlock success!", currLockId);
			} else {
				log.warn("rlock is null maybe expired!");
			}
		} else {
			log.info("lockId={} not holder by current thread so not need unlock!", currLockId);
		}
	}

	public RedissonClient getRedisson() {
		return redisson;
	}

	public void setRedisson(RedissonClient redisson) {
		this.redisson = redisson;
	}

	public String getNamespace() {
		return namespace;
	}

	public void setNamespace(String namespace) {
		this.namespace = namespace;
	}

	public static void main(String[] args) {
		//main方法调试设置日志级别
		LogbackUtil.setLogLevel("info");

		Config config = new Config();
		config.useSingleServer()
				.setAddress("redis://81.68.133.219:4000")
				.setPassword("@Redis,.123");
		RedissonClient redisson = Redisson.create(config);
		DistributeLock lock = new DistributeLock(redisson);

		int doSize = 100;
		ThreadPoolExecutor executor = new ThreadPoolExecutor(10, 10, 0, TimeUnit.SECONDS, new ArrayBlockingQueue<Runnable>(doSize));
		CountDownLatch startSignal = new CountDownLatch(1);
		CountDownLatch doneSignal = new CountDownLatch(doSize);

		for (int i = 0; i < doSize; i++) {
			executor.execute(() -> {
				try {
					startSignal.await();
				} catch (Exception e) {
					e.printStackTrace();
				}
				try {
					lock.lock("RRR");
					try {
						lock.testSum++;
					} finally {
						lock.unlock();
					}
				} finally {
					doneSignal.countDown();
				}
			});
		}
		startSignal.countDown();
		try {
			doneSignal.await();
		} catch (InterruptedException e) {
			e.printStackTrace();
		}
		executor.shutdown();
		redisson.shutdown();
		System.out.println(lock.testSum);
	}
}
