package com.bizzan.bitrade.service;

import java.util.Date;
import java.util.Random;
import java.util.concurrent.TimeUnit;

import javax.annotation.Resource;

import org.springframework.beans.factory.annotation.Autowired;
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.SetOperations;
import org.springframework.data.redis.core.ValueOperations;
import org.springframework.stereotype.Component;

import lombok.extern.slf4j.Slf4j;

@Slf4j
@Component
public class RedisLock {
	@Autowired
    private RedisTemplate<Object,Object> redisTemplate;

    @Resource(name = "redisTemplate")
    private ValueOperations<Object,Object> valOps;

    @Resource(name = "redisTemplate")
    private SetOperations<Object,Object> setOps;

    /**
     * @Title:
     * @Description:   保存K-V,并设置时间
     * @return
     * @throws
     * @author qiaomengnan
     * @date 2019/06/28 01:51:32
     */
    public void save(Object key, Object value, long time){
        valOps.set(key,value,time,TimeUnit.SECONDS);
    }

    /**
     * @Title:
     * @Description:   获取过期时间
     * @param key
     * @return
     * @throws
     * @author qiaomengnan
     * @date 2019/11/01 10:22:46
     */
    public Long getExpire(Object key) {
        return redisTemplate.getExpire(key);
    }

    /**
     * @Title:
     * @Description:   保存K-V,并设置时间
     * @return
     * @throws
     * @author qiaomengnan
     * @date 2019/06/28 01:51:32
     */
    public Object get(Object key){
        return valOps.get(key);
    }

    /**
     * @Title:
     * @Description:   保存K-V
     * @return
     * @throws
     * @author qiaomengnan
     * @date 2019/06/28 01:51:32
     */
    public void saveSetValue(Object key , Object ...value) {
        setOps.add(key, value);
    }

    /**
     * @Title:
     * @Description:   保存K-V
     * @return
     * @throws
     * @author qiaomengnan
     * @date 2019/06/28 01:51:32
     */
    public void saveSetValue(Object key , long time , Object ...value) {
        setOps.add(key, value,time,TimeUnit.SECONDS);
    }

    /**
     * @Title:
     * @Description:   判断一个key是否存在
     * @param key
     * @return
     * @throws
     * @author qiaomengnan
     * @date 2019/08/10 03:11:41
     */
    public boolean hasKey(Object key) {
        return redisTemplate.hasKey(key);
    }

    /**
     * @Title:
     * @Description:   删除key
     * @param key
     * @return
     * @throws
     * @author qiaomengnan
     * @date 2019/09/04 09:30:18
     */
    public void delete(Object key) {
        redisTemplate.delete(key);
    }

    /**
     * @Title:
     * @Description:   获取Long类型的值
     * @param key 键
     * @return
     * @throws
     * @author qiaomengnan
     * @date 2019/08/07 04:47:58
     */
    public Long getLong(Object key) {
        try {
            Object res = valOps.get(key);
            return res == null ? null : Long.parseLong(res.toString());
        }catch (Exception ex) {
            ex.printStackTrace();
        }
        return null;
    }
    
    private boolean setNX(final String key, final String value) {
        return redisTemplate.opsForValue().setIfAbsent(key, value);
    }

    public boolean lockBySeconds(String key, int seconds){
    	Long nowTime = new Date().getTime();
        Long exprirationTime = nowTime + (seconds * 1000);
        // 通过setNx获取锁
        boolean absent = setNX(key, String.valueOf(exprirationTime));
        if(!absent) {
        	Object res = redisTemplate.opsForValue().get(key);
        	if(res == null) {
        		redisTemplate.delete(key);
        		return setNX(key, String.valueOf(exprirationTime));
        	}
        	Long absentExprirationTime = Long.parseLong(res.toString());
        	log.info("exprirationTime={}, absentExprirationTime={}, nowTime={}", exprirationTime,absentExprirationTime,nowTime);
        	if(absentExprirationTime == null || absentExprirationTime<nowTime) {
        		redisTemplate.delete(key);
        		return setNX(key, String.valueOf(exprirationTime));
        	}
        }
        return absent;
    }
    
    public boolean waitLockWithSeconds(String key, long timeout, int seconds) {
    	long millis = System.currentTimeMillis();
        timeout *= 1000;
        try {
            while ((System.currentTimeMillis() - millis) < timeout) {
                if (lockBySeconds(key, seconds)) {
                    return true;
                }
                log.info("Getting lock failed, wait for 20 milliseconds");
                Thread.sleep(20, (int)(Math.random() * 500));
            }
            log.info("Getting lock failed, timeout");
        } catch (Exception e) {
            throw new RuntimeException("Jedis 在加锁过程中key:" + key+ "进行加锁失败!!!", e);
        }
        log.info("Getting lock failed, timeout2");
        return false;
    }
    
    public void unlock(String key) {
    	redisTemplate.delete(key);
    }
    
    public boolean lockWalletForClose(Long walletId) {
    	String key = "WALLET_LOCK_FOR_CLOSE:" + walletId;
    	return this.lockBySeconds(key, 3);
    }
    
    public void unlockWalletForClose(Long walletId) {
    	String key = "WALLET_LOCK_FOR_CLOSE:" + walletId;
    	this.unlock(key);
    }
    
    public boolean lockPosition(Long positionId) {
    	String key = "POSITION_LOCK:" + positionId;
    	return this.lockBySeconds(key, 3);
    }
    
    public boolean waitPositionLock(Long positionId) {
    	String key = "POSITION_LOCK:" + positionId;
    	return this.waitLockWithSeconds(key, 10, 3);
    }
    
    public void unlockPosition(Long positionId) {
    	String key = "POSITION_LOCK:" + positionId;
    	this.unlock(key);
    }
    
    public boolean lockTradingAccount(Long memberId) {
    	String key = "TRADING_ACCOUNT_LOCK:" + memberId;
    	return this.lockBySeconds(key, 3);
    }
    
    public boolean waitTradingAccountLock(Long memberId) {
    	String key = "TRADING_ACCOUNT_LOCK:" + memberId;
    	return this.waitLockWithSeconds(key, 10, 3);
    }
    
    public void unlockTradingAccount(Long memberId) {
    	String key = "TRADING_ACCOUNT_LOCK:" + memberId;
    	this.unlock(key);
    }
    
    public boolean lockFollowAccount(Long memberId) {
    	String key = "FOLLOW_ACCOUNT_LOCK:" + memberId;
    	return this.lockBySeconds(key, 3);
    }
    
    public boolean waitFollowAccountLock(Long memberId) {
    	String key = "FOLLOW_ACCOUNT_LOCK:" + memberId;
    	return this.waitLockWithSeconds(key, 10, 3);
    }
    
    public void unlockFollowAccount(Long memberId) {
    	String key = "FOLLOW_ACCOUNT_LOCK:" + memberId;
    	this.unlock(key);
    }
    
    public void holdFollowPosition(Long positionId) {
    	String key = "FOLLOW_POSITION_LOCK:" + positionId;
    	this.save(key, 1, 5);
    }
    
    public boolean hasFollowPosition(Long positionId) {
    	String key = "FOLLOW_POSITION_LOCK:" + positionId;
    	return this.hasKey(key);
    }
    
    public void releaseFollowPosition(Long positionId) {
    	String key = "FOLLOW_POSITION_LOCK:" + positionId;
    	this.delete(key);
    }

    public void lockBatchOpen(Long positionId) {
    	String key = "CT_BATCH_OPEN_LOCK:" + positionId;
    	this.save(key, 1, 60);
    }

    public void unlockBatchOpen(Long positionId) {
    	String key = "CT_BATCH_OPEN_LOCK:" + positionId;
    	this.delete(key);
    }

    public boolean hasBatchOpen(Long positionId) {
    	String key = "CT_BATCH_OPEN_LOCK:" + positionId;
    	return this.hasKey(key);
    }

    public void lockFollowBatchOpen(Long traderPositionId) {
    	String key = "FOLLOW_BATCH_OPEN_LOCK:" + traderPositionId;
    	this.save(key, 1, 300);
    }

    public void unlockFollowBatchOpen(Long traderPositionId) {
    	String key = "FOLLOW_BATCH_OPEN_LOCK:" + traderPositionId;
    	this.delete(key);
    }

    public boolean hasFollowBatchOpen(Long traderPositionId) {
    	String key = "FOLLOW_BATCH_OPEN_LOCK:" + traderPositionId;
    	return this.hasKey(key);
    }

    public void saveFollowBatchOpenCount(Long traderPositionId, int count) {
    	String key = "FOLLOW_BATCH_OPEN_COUNT:" + traderPositionId;
    	this.save(key, count, 120);
    }

    public Integer getFollowBatchOpenCount(Long traderPositionId) {
    	String key = "FOLLOW_BATCH_OPEN_COUNT:" + traderPositionId;
    	Object val = redisTemplate.opsForValue().get(key);
    	if (val == null) return null;
    	return Integer.parseInt(val.toString());
    }

    public boolean hasFollowBatchOpenCount(Long traderPositionId) {
    	String key = "FOLLOW_BATCH_OPEN_COUNT:" + traderPositionId;
    	return this.hasKey(key);
    }

    public boolean lockSpecialFollowAccount(Long memberId) {
    	String key = "SPECIAL_FOLLOW_ACCOUNT_LOCK:" + memberId;
    	return this.lockBySeconds(key, 3);
    }
    
    public boolean waitSpecialFollowAccountLock(Long memberId) {
    	String key = "SPECIAL_FOLLOW_ACCOUNT_LOCK:" + memberId;
    	return this.waitLockWithSeconds(key, 10, 3);
    }
    
    public void unlockSpecialFollowAccount(Long memberId) {
    	String key = "SPECIAL_FOLLOW_ACCOUNT_LOCK:" + memberId;
    	this.unlock(key);
    }
    
    public boolean lockOrder(Long orderId) {
    	String key = "ORDER_LOCK:" + orderId;
    	return this.lockBySeconds(key, 3);
    }

    public void unlockOrder(Long orderId) {
    	String key = "ORDER_LOCK:" + orderId;
    	this.unlock(key);
    }

    public boolean lockExperienceGiftRecord(Long recordId) {
        String key = "EG_RECORD_LOCK:" + recordId;
        return this.lockBySeconds(key, 3);
    }

    public void unlockExperienceGiftRecord(Long recordId) {
        String key = "EG_RECORD_LOCK:" + recordId;
        this.unlock(key);
    }

}
