package com.qmth.boot.core.uid;

/**
 * SnowFlake算法修改版生成递增分布式ID<br/>
 *
 * @author luoshi
 */
public class UidGenerator {

    // 起始时间戳，2020-06-01 00:00:00
    private final static long START_TIMESTAMP = 1590940800000L;

    // 序列号占用的位数
    private final static long SEQUENCE_BIT = 12;

    // 数据中心编号占用的位数
    private final static long DATACENTER_BIT = 3;

    // 机器编号占用的位数
    private final static long MACHINE_BIT = 7;

    // 最大数据中心编号7
    public final static long MAX_DATACENTER_ID = ~(-1L << DATACENTER_BIT);

    // 最大机器编号127
    public final static long MAX_MACHINE_ID = ~(-1L << MACHINE_BIT);

    // 最大序列号
    private final static long MAX_SEQUENCE = ~(-1L << SEQUENCE_BIT);

    /**
     * 每一部分向左的位移
     */
    private final static long MACHINE_LEFT = SEQUENCE_BIT;

    private final static long DATACENTER_LEFT = MACHINE_LEFT + MACHINE_BIT;

    private final static long TIMESTMP_LEFT = DATACENTER_LEFT + DATACENTER_BIT;

    // 数据中心编号
    private long datacenterId;

    // 机器编号
    private long machineId;

    // 序列号
    private long sequence = 0L;

    // 上一次时间戳
    private long lastTimestamp = -1L;

    public UidGenerator(long datacenterId, long machineId) {
        if (datacenterId > MAX_DATACENTER_ID || datacenterId < 0) {
            throw new IllegalArgumentException(
                    "datacenterId can't be greater than " + MAX_DATACENTER_ID + " or less than 0");
        }
        if (machineId > MAX_MACHINE_ID || machineId < 0) {
            throw new IllegalArgumentException("machineId can't be greater than " + MAX_MACHINE_ID + " or less than 0");
        }
        this.datacenterId = datacenterId;
        this.machineId = machineId;
    }

    /**
     * 产生下一个UID
     *
     * @return
     */
    public synchronized long next() {
        long current = System.currentTimeMillis();
        // 不能在程序运行过程中回拨时钟，否则报异常需要等待时钟赶上后重试
        if (current < lastTimestamp) {
            throw new RuntimeException("Clock moved backwards.  Refusing to generate uid");
        }

        if (current == lastTimestamp) {
            // 相同毫秒内，序列号自增
            sequence = (sequence + 1) & MAX_SEQUENCE;
            // 同一毫秒的序列号已经用完，等待延迟到下一毫秒
            if (sequence == 0L) {
                current = waitNextMillisecond();
            }
        } else {
            // 不同毫秒内，序列号置为0
            sequence = 0L;
        }

        lastTimestamp = current;

        return (current - START_TIMESTAMP) << TIMESTMP_LEFT // 时间戳部分
                | (datacenterId << DATACENTER_LEFT) //数据中心部分
                | (machineId << MACHINE_LEFT) // 机器部分
                | sequence; // 序列号部分
    }

    private long waitNextMillisecond() {
        long time = System.currentTimeMillis();
        while (time <= lastTimestamp) {
            time = System.currentTimeMillis();
        }
        return time;
    }

}
