package com.qmth.themis.business.util;

import com.qmth.themis.business.constant.SpringContextHolder;
import com.qmth.themis.business.domain.SysDomain;
import com.qmth.themis.common.uid.UidGenerator;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.scheduling.annotation.EnableScheduling;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Component;

import javax.annotation.PostConstruct;
import javax.annotation.PreDestroy;
import javax.annotation.Resource;
import java.util.concurrent.TimeUnit;

@Component
@EnableScheduling
public class UidUtil {

    private static final Logger log = LoggerFactory.getLogger(UidUtil.class);

    @Resource
    private SysDomain sysDomain;

    @Resource
    RedisTemplate<String, Object> redisTemplate;

    private static final String MACHINE_ID_KEY = "machine_id_%d";

    private static final int MACHINE_ID_EXPIRE_MINUTES = 60;

    private int machineId;

    private UidGenerator uid;

    @PostConstruct
    public void init() {
        int datacenterId = sysDomain.getDatacenterId();
        if (datacenterId <= 0 || datacenterId > UidGenerator.MAX_DATACENTER_ID) {
            throw new IllegalArgumentException(
                    "Invalid datacenterId, should between 1 and " + UidGenerator.MAX_DATACENTER_ID);
        }
        machineId = 0;
        for (int i = 1; i <= UidGenerator.MAX_MACHINE_ID; i++) {
            if (registerMachineId(i)) {
                machineId = i;
                break;
            }
        }
        if (machineId == 0) {
            throw new IllegalArgumentException("Register machineId faile");
        }
        uid = new UidGenerator(datacenterId, machineId);
        log.info("Uid init success, datacenterId=" + datacenterId + ", machineId=" + machineId);
    }

    private boolean registerMachineId(int machineId) {
        String key = String.format(MACHINE_ID_KEY, machineId);
        Boolean result = redisTemplate.opsForValue()
                .setIfAbsent(key, System.currentTimeMillis(), MACHINE_ID_EXPIRE_MINUTES, TimeUnit.MINUTES);
        return result != null && result;
    }

    /**
     * 定时续租machineId
     */
    @Scheduled(cron = "0 0/15 * * * ?")
    private void renewMachineId() {
        log.info("Start renew machindId");
        String key = String.format(MACHINE_ID_KEY, machineId);
        Boolean result = redisTemplate.expire(key, MACHINE_ID_EXPIRE_MINUTES, TimeUnit.MINUTES);
        if (result == null || !result) {
            log.warn("Renew machineId faile, machineId=" + machineId);
        }
    }

    @PreDestroy
    public void destroy() {
        log.info("Start release machindId");
        String key = String.format(MACHINE_ID_KEY, machineId);
        Boolean result = redisTemplate.delete(key);
        if (result == null || !result) {
            log.warn("Release machineId faile, machineId=" + machineId);
        }
    }

    public long getId() {
        return uid.next();
    }

    public static long nextId() {
        return SpringContextHolder.getBean(UidUtil.class).getId();
    }
}
