package com.tykj.dev.misc.utils;

import org.springframework.context.annotation.Configuration;
import org.springframework.stereotype.Component;

import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

/**
 * @author dengdiyi
 */
@Component
@Configuration
public class Snowflake {
    /**
     * 开始时间戳
     */
    private static final long START_TIME = 1620355582082L;
    /**
     * 时间戳位数
     */
    private static final long TIME_BIT = 39L;
    /**
     * 机房组位数
     */
    private static final long WORKER_GROUP_BIT = 0L;
    /**
     * 机器位数
     */
    private static final long WORKER_BIT = 0L;
    /**
     * 标识符位数
     */
    private static final long SEQUENCE_BIT = 5L;
    /**
     * 最大机房ID
     */
    private static final long MAX_WORKER_GROUP = ~(-1L << WORKER_GROUP_BIT);
    /**
     * 最大机器ID
     */
    private static final long MAX_WORKER = ~(-1L << WORKER_BIT);
    /**
     * 时钟回拨位数
     */
    private static final long CLOCK_BACK_BIT = 0L;
    /**
     * 最大时钟回拨数
     */
    private static final long MAX_CLOCK_BACK = ~(-1L << CLOCK_BACK_BIT);
    /**
     * 时钟回拨最大等待时间
     */
    private static final long MAX_WAIT_TIME = 10000L;
    /**
     * 最大Seq
     */
    private static final long MAX_SEQUENCE = ~(-1L << SEQUENCE_BIT);
    /**
     * 机器ID左移位数
     */
    private static final long WORKER_ID_LEFT_SHIFT = SEQUENCE_BIT;
    /**
     * 机房ID左移位数
     */
    private static final long WORKER_GROUP_ID_LEFT_SHIFT = SEQUENCE_BIT + WORKER_BIT;
    /**
     * 时间戳左移位数
     */
    private static final long TIME_LEFT_SHIFT = SEQUENCE_BIT + WORKER_BIT + WORKER_GROUP_BIT;
    /**
     * 时间回拨位左移位数
     */
    private static final long CLOCK_BACK_SHIFT = SEQUENCE_BIT + WORKER_BIT + WORKER_GROUP_BIT + TIME_BIT;
    /**
     * lock锁实现同步
     */
    private final Lock lock = new ReentrantLock();
    /**
     * 上一次的时间戳
     */
    private long lastTime = -1L;
    /**
     * 当前的时间戳
     */
    private long timeNow;
    /**
     * 当前时钟回拨数
     */
    private long clockBack = 0L;
    /**
     * 当前标识符
     */
    private long sequence = 0L;
    /**
     * 当前机房ID
     */
    private long workGroupId = 0L;
    /**
     * 当前机器ID
     */
    private long workerId = 0L;

//    public int num =0;

    /**
     * 设置当前机器ID
     *
     * @param workerId 机器ID
     */
    public void setWorkerId(long workerId) {
        if (workerId < MAX_WORKER && workerId >= 0) {
            this.workerId = workerId;
        } else {
            throw new IllegalArgumentException("workerId is too big or a minus");
        }
    }

    /**
     * 设置当前机房ID
     *
     * @param workGroupId 机房ID
     */
    public void setWorkGroupId(long workGroupId) {
        if (workGroupId < MAX_WORKER_GROUP && workGroupId >= 0) {
            this.workGroupId = workGroupId;
        } else {
            throw new IllegalArgumentException("workGroup_id is too big or a minus");
        }
    }

    /**
     * 同步创建下一个订单号
     *
     * @return long id
     */
    public long creatNextId() {
        lock.lock();
        try {
            timeNow = System.currentTimeMillis();
            if (timeNow == lastTime) {
                sequence = (sequence + 1) & MAX_SEQUENCE;
                if (sequence == 0) {
                    waitNextMills();
                }
            } else if (isClockBack()) {
                if (lastTime - timeNow < MAX_WAIT_TIME) {
                    try {
                        Thread.sleep(lastTime - timeNow);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                } else {
                    clockBack = (clockBack + 1) & MAX_CLOCK_BACK;
                    if (clockBack == 0) {
                        throw new IllegalArgumentException("clock back too many times");
                    }
                    sequence = 0L;
                }
            } else {
                sequence = 0L;
            }
            lastTime = timeNow;
            return sequence | workerId << WORKER_ID_LEFT_SHIFT | workGroupId << WORKER_GROUP_ID_LEFT_SHIFT | (timeNow - START_TIME) << TIME_LEFT_SHIFT | clockBack << CLOCK_BACK_SHIFT;
        } finally {
            lock.unlock();
        }
    }

    /**
     * 判断是否时钟回拨
     *
     * @return true or false
     */
    public boolean isClockBack() {
        return lastTime - timeNow > 0;
    }

    /**
     * 等待下一毫秒
     */
    public void waitNextMills() {
        timeNow = System.currentTimeMillis();
        while (timeNow <= lastTime) {
            timeNow = System.currentTimeMillis();
        }
    }
}
