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

import java.util.HashSet;
import java.util.Set;
import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.ReentrantLock;

import org.springframework.util.Assert;

import com.crunii.micro.common.exception.MicroException;
import com.github.benmanes.caffeine.cache.Cache;
import com.github.benmanes.caffeine.cache.Caffeine;

import lombok.extern.slf4j.Slf4j;

/**
 * @author 田平 create 2012-10-11下午12:39:36
 */
@Slf4j
public class LockUtil {

	private static final int MIN_TIMEOUT = 5000;// 毫秒

	private static final int MAX_TIMEOUT = 30000;// 毫秒

	private static Cache<String, ReentrantLock> locks = Caffeine.newBuilder()
			.expireAfterAccess(3, TimeUnit.MINUTES) //必须使用最后一次访问后多久过期
			.initialCapacity(10000)
			.build();

	private static ThreadLocal<Set<String>> currLockIds = new ThreadLocal<Set<String>>() {
		protected Set<String> initialValue() {
			return new HashSet<String>(6);
		}
	};

	private static int testNum = 0;

	public static void lock(String lockId) {
		Assert.hasText(lockId, "lockId not allow null!");
		int timeout = getRandom(MIN_TIMEOUT, MAX_TIMEOUT);
		ReentrantLock lock = locks.get(lockId, key -> new ReentrantLock());
		try {
			Set<String> set = currLockIds.get();
			if (!lock.isHeldByCurrentThread()) {
				boolean lockSucc = lock.tryLock(timeout, TimeUnit.MILLISECONDS);
				if (!lockSucc) {
					throw new MicroException(MicroException.LOCK_FAILURE, lockId, timeout);
				}
				currLockIds.get().add(lockId);
				log.debug("lock success lockId:{} currLockIds:{} timeout:{} ms!", lockId, set, timeout);
			} else {
				log.warn("lockId:{} already locked currLockIds:{} timeout:{} ms!", lockId, set, timeout);
			}
		} catch (Exception e) {
			throw new MicroException(MicroException.LOCK_FAILURE, e, lockId);
		}
	}

	public static void unlock() {
		Set<String> set = currLockIds.get();
		try {
			set.stream().forEach(lockId -> rmlock(lockId));
			log.debug("unlock success currLockIds:{}", set);
		} finally {
			set.clear();
		}
	}

	private static void rmlock(String lockId) {
		ReentrantLock lock = locks.getIfPresent(lockId);
		if (lock != null) {
			while (lock.isHeldByCurrentThread() && lock.getHoldCount() > 0) {
				log.debug("rmlock lockId:{} holdCount:{}", lockId, lock.getHoldCount());
				lock.unlock();
			}
		} else {
			log.warn("lockId:{} rmlock is null!", lockId);
		}
	}

	public static int getRandom(int min, int max) {

		return (int) (min + Math.random() * (max - min + 1));
	}

	public static void main(String[] args) {
		int doSize = 10000;
		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(new Runnable() {
				public void run() {
					try {
						startSignal.await();
					} catch (InterruptedException e) {
						log.error("startSignal.await error!", e);
					}
					try {
						try {
							LockUtil.lock("ECH-Test-Num");
							LockUtil.testNum++;
						} finally {
							LockUtil.unlock();
						}
					} finally {
						doneSignal.countDown();
					}

				}
			});
		}
		startSignal.countDown();
		try {
			doneSignal.await();
		} catch (InterruptedException e) {
			e.printStackTrace();
		}
		executor.shutdown();
		System.out.println(LockUtil.testNum);
	}

}
