package com.vctgo.file.utils;

/**
 * 雪花Id生成器
 *
 * @author wanglonghua
 * @date 2022/4/6 10:32 下午
 */
public class SnowflakeIdGenerator {

    /**
     * 单例
     */
    public static SnowflakeIdGenerator instance = new SnowflakeIdGenerator();

    /**
     * 初始化单例
     *
     * @Param workerId 最大值8192
     */
    public synchronized void init(long workerId) {
        if (workerId > MAX_WORKER_ID) {
            //ZK分配的workerId过大
            throw new IllegalArgumentException("woker Id wrong: " + workerId);
        }
        instance.workerId = workerId;
    }

    private SnowflakeIdGenerator() {
    }

/*    public static void main(String[] args) {
        //new SnowflakeIdTest().snowflakeIdTest();

        //初始化id生成器
        SnowflakeIdGenerator.instance.init(1);
        System.out.println(" ID001：" + SnowflakeIdGenerator.instance.nextId());
        System.out.println(" ID001：" + SnowflakeIdGenerator.instance.nextId());

        SnowflakeIdGenerator.instance.init(100);
        System.out.println(" ID100：" + SnowflakeIdGenerator.instance.nextId());
        System.out.println(" ID100：" + SnowflakeIdGenerator.instance.nextId());

        SnowflakeIdGenerator.instance.init(300);
        System.out.println(" ID300：" + SnowflakeIdGenerator.instance.nextId());
        System.out.println(" ID300：" + SnowflakeIdGenerator.instance.nextId());

        SnowflakeIdGenerator.instance.init(2145);
        System.out.println(" ID2145：" + SnowflakeIdGenerator.instance.nextId());
        System.out.println(" ID2145：" + SnowflakeIdGenerator.instance.nextId());

        SnowflakeIdGenerator.instance.init(7634);
        System.out.println(" ID7634：" + SnowflakeIdGenerator.instance.nextId());
        System.out.println(" ID7634：" + SnowflakeIdGenerator.instance.nextId());
        System.out.println(" ID7634 length：" + String.valueOf(SnowflakeIdGenerator.instance.nextId()).length());
    }*/

    /**
     * 开始使用该算法的时间为2017-01-01 00:00:00
     */
    private static final long START_TIME = 1483200000000L;

    /**
     * worker id 的位数，最多支持8192个节点
     */
    private static final int WORKER_ID_BITS = 13;

    /**
     * 序列号，支持单节点最高每毫秒的最大ID数1024
     */
    private final static int SEQUENCE_BITS = 10;

    /**
     * 最大的 worker id，8091
     * -1 的补码（二进制全1）右移13位，然后取反
     */
    private final static long MAX_WORKER_ID = ~(-1L << WORKER_ID_BITS);

    /**
     * 最大的序列号，1023
     * -1 的补码（二进制全1）右移10位, 然后取反
     */
    private final static long MAX_SEQUENCE = ~(-1L << SEQUENCE_BITS);

    /**
     * worker 节点编号的移位
     */
    private final static long APP_HOST_ID_SHIFT = SEQUENCE_BITS;

    /**
     * 时间戳的移位
     */
    private final static long TIMESTAMP_LEFT_SHIFT = WORKER_ID_BITS + APP_HOST_ID_SHIFT;

    /**
     * 该项目的worker 节点 id
     */
    private long workerId;

    /**
     * 上次生成ID的时间戳
     */
    private long lastTimestamp = -1L;

    /**
     * 当前毫秒生成的序列
     */
    private long sequence = 0L;

    /**
     * Next id long.
     *
     * @return the nextId
     */
    public Long nextId() {
        return generateId();
    }

    /**
     * 生成唯一ID的具体实现
     */
    private synchronized long generateId() {
        long current = System.currentTimeMillis();

        if (current < lastTimestamp) {
            //如果当前时间小于上一次ID生成的时间戳
            //说明系统时钟回退过，出现问题返回-1，生成ID失败
            return -1;
        }

        if (current == lastTimestamp) {
            //如果当前生成ID的时间还是上次的时间，那么对sequence序列号进行+1
            sequence = (sequence + 1) & MAX_SEQUENCE;
            if (sequence == MAX_SEQUENCE) {
                //当前毫秒生成的序列数已经大于最大值
                //那么阻塞到下一毫秒再获取新的时间戳
                current = this.nextMs(lastTimestamp);
            }
        } else {
            //当前的时间戳已经是下一毫秒
            sequence = 0L;
        }

        //更新上次生成ID的时间戳
        lastTimestamp = current;

        //进行移位操作生成int64的唯一ID
        //时间戳右移23位
        long time = (current - START_TIME) << TIMESTAMP_LEFT_SHIFT;

        //workerId 右移10位
        long workerId = this.workerId << APP_HOST_ID_SHIFT;

        return time | workerId | sequence; //返回ID
    }

    /**
     * 阻塞到下一毫秒
     */
    private long nextMs(long timeStamp) {
        long current = System.currentTimeMillis();
        while (current <= timeStamp) {
            current = System.currentTimeMillis();
        }
        return current;
    }

}

