package com.eshore.gringotts.web.configuration;

import java.io.Serializable;
import java.time.Instant;
import org.hibernate.engine.spi.SharedSessionContractImplementor;
import org.hibernate.id.IdentifierGenerator;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
 * 使用雪花算法作为ID生成器
 *
 * @author lanyuanxiaoyao
 * @date 2024-11-14
 */
public class SnowflakeIdGenerator implements IdentifierGenerator {
    private static final Logger logger = LoggerFactory.getLogger(SnowflakeIdGenerator.class);

    @Override
    public Serializable generate(SharedSessionContractImplementor session, Object object) {
        try {
            return Snowflake.next();
        } catch (Exception e) {
            logger.error("Generate snowflake id failed", e);
            throw new RuntimeException(e);
        }
    }

    private static class Snowflake {
        /**
         * 起始的时间戳
         */
        private final static long START_TIMESTAMP = 1;

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

        /**
         * 序列号最大值
         */
        private final static long MAX_SEQUENCE_BIT = ~(-1 << SEQUENCE_BIT);

        /**
         * 时间戳值向左位移
         */
        private final static long TIMESTAMP_OFFSET = SEQUENCE_BIT;

        /**
         * 序列号
         */
        private static long sequence = 0;
        /**
         * 上一次时间戳
         */
        private static long lastTimestamp = -1;

        public static synchronized long next() {
            long currentTimestamp = nowTimestamp();
            if (currentTimestamp < lastTimestamp) {
                throw new RuntimeException("Clock have moved backwards.");
            }

            if (currentTimestamp == lastTimestamp) {
                // 相同毫秒内, 序列号自增
                sequence = (sequence + 1) & MAX_SEQUENCE_BIT;
                // 同一毫秒的序列数已经达到最大
                if (sequence == 0) {
                    currentTimestamp = nextTimestamp();
                }
            } else {
                // 不同毫秒内, 序列号置为0
                sequence = 0;
            }

            lastTimestamp = currentTimestamp;
            return (currentTimestamp - START_TIMESTAMP) << TIMESTAMP_OFFSET | sequence;
        }

        private static long nextTimestamp() {
            long milli = nowTimestamp();
            while (milli <= lastTimestamp) {
                milli = nowTimestamp();
            }
            return milli;
        }

        private static long nowTimestamp() {
            return Instant.now().toEpochMilli();
        }
    }
}
