package com.xingxi.common.utils.uuid;


import org.apache.commons.lang3.RandomStringUtils;

/**
 * snowflake id 生成器
 *
 * @author max woods
 * @date 2018/8/14
 */

public class IdWorker {

    private static final long TWEPOCH = 1288834974657L;
    /**
     * 机器标识位数
     */
    private static final long WORKER_ID_BITS = 5L;
    /**
     * 数据中心标识位数
     */
    private static final long DATACENTER_ID_BITS = 5L;
    /**
     * 机器ID最大值 31
     */
    private static final long MAX_WORKER_ID = -1L ^ (-1L << IdWorker.WORKER_ID_BITS);
    /**
     * 数据中心ID最大值 31
     */
    private static final long MAX_DATACENTER_ID = -1L ^ (-1L << IdWorker.DATACENTER_ID_BITS);
    /**
     * 毫秒内自增位
     */
    private static final long SEQUENCE_BITS = 12L;
    /**
     * 机器ID偏左移12位
     */
    private static final long WORKER_ID_SHIFT = IdWorker.SEQUENCE_BITS;
    private static final long DATACENTER_ID_SHIFT = IdWorker.SEQUENCE_BITS + IdWorker.WORKER_ID_BITS;
    /**
     * 时间毫秒左移22位
     */
    private static final long TIMESTAMP_LEFT_SHIFT = IdWorker.SEQUENCE_BITS + IdWorker.WORKER_ID_BITS
            + IdWorker.DATACENTER_ID_BITS;
    private static final long SEQUENCE_MASK = -1L ^ (-1L << IdWorker.SEQUENCE_BITS);

    private final long workerId;
    private final long datacenterId;
    private long sequence = 0L;
    private long lastTimestamp = -1L;

    public IdWorker(long workerId, long datacenterId) {
        if (workerId > IdWorker.MAX_WORKER_ID || workerId < 0) {
            throw new IllegalArgumentException(String.format("worker Id can't be greater than %d or less than 0",
                    IdWorker.MAX_WORKER_ID));
        }
        if (datacenterId > IdWorker.MAX_DATACENTER_ID || datacenterId < 0) {
            throw new IllegalArgumentException(String.format("datacenter Id can't be greater than %d or less than 0",
                    IdWorker.MAX_DATACENTER_ID));
        }
        this.workerId = workerId;
        this.datacenterId = datacenterId;
    }

    public synchronized long nextId() {
        long timestamp = timeGen();
        if (timestamp < lastTimestamp) {
            throw new RuntimeException(String.format(
                    "Clock moved backwards.  Refusing to generate id for %d milliseconds", lastTimestamp - timestamp));
        }
        if (lastTimestamp == timestamp) {
            sequence = (sequence + 1) & IdWorker.SEQUENCE_MASK;
            if (sequence == 0) {
                timestamp = tilNextMillis(lastTimestamp);
            }
        } else {
            sequence = 0L;
        }

        lastTimestamp = timestamp;

        return ((timestamp - IdWorker.TWEPOCH) << IdWorker.TIMESTAMP_LEFT_SHIFT)
                | (datacenterId << IdWorker.DATACENTER_ID_SHIFT) | (workerId << IdWorker.WORKER_ID_SHIFT) | sequence;
    }

    private long tilNextMillis(long lastTimestamp) {
        long timestamp = timeGen();
        while (timestamp <= lastTimestamp) {
            timestamp = timeGen();
        }
        return timestamp;
    }

    private long timeGen() {
        return System.currentTimeMillis();
    }


    public static void main(String[] args) {
        IdWorker worker = new IdWorker(2, 23);
        for (int i = 0; i < 30; i++) {
            System.out.println(worker.nextId());
        }


        System.out.println(RandomStringUtils.randomNumeric(20));

//        RandomStringUtils.randomAlphanumeric(20);
    }
}