<?php

namespace Dipper\Foundation\Snowflake;

use Exception;

/**
 * ID 生成策略
 * 毫秒级时间41位 + 工作机器ID 10位 + 毫秒内序列12位。
 * 0           41     49     64
 * +-----------+------+------+
 * |time       |pc    |inc   |
 * +-----------+------+------+
 *  前41bits是以微秒为单位的timestamp，可用69年。
 *  接着10bits是事先配置好的工作机器ID，最多支持1024台机器。
 *  最后12bits是累加计数器，1个工作机器1ms中最多产生4096个ID。
 */
class IdWorker implements IdGenerator
{
    /**
     * 工作机器ID位数
     */
    const WORKER_ID_BITS = 10;

    /**
     * 序列位数
     */
    const SEQUENCE_BITS = 12;

    /**
     * 起始毫秒时间 | 从2010年01月01日起计算
     */
    const TWEPOC = 1262275200000;

    /**
     * 工作机器ID
     *
     * @var int
     */
    protected $macheineId;

    /**
     * 序列号
     *
     * @var int
     */
    protected $sequence;

    /**
     * 最后时间
     *
     * @var int
     */
    protected $lastTimestamp = -1;

    /**
     * 构造函数
     *
     * @param int $macheineId
     * @param int $sequence
     */
    public function __construct($macheineId, $sequence = 0)
    {
        $this->setWorkerId($macheineId);
        $this->sequence = $sequence;
    }
    /**
     * 返回下一个 Snowflake ID.
     *
     * @return string|biginteger
     * @throws Exception
     */
    public function nextId()
    {
        if (PHP_INT_SIZE === 4) {
            throw new Exception('不支持生成int32类型的ID');
        }

        $timestamp = $this->getTimestamp();

        if ($timestamp < $this->lastTimestamp) {
            throw new Exception('系统时钟错误，当前时间差' . ($this->lastTimestamp - $timestamp));
        }

        if ($timestamp == $this->lastTimestamp) {
            $sequence = $this->nextSequence() & $this->sequenceMask();
            // sequence rollover, wait til next millisecond
            if ($sequence == 0) {
                $timestamp = $this->tilNextMillis($this->lastTimestamp);
            }
        } else {
            $this->sequence = 0;
            $sequence = $this->nextSequence();
        }
        $this->lastTimestamp = $timestamp;

        $t = floor($timestamp - self::TWEPOC) << $this->timestampLeftShift();

        $macheineId = $this->getWorkerId() << $this->macheineIdShift();

        return (string) $t | $macheineId | $sequence;
    }

    /**
     * 获取当前毫秒时间戳
     *
     * @return int
     */
    public function getTimestamp()
    {
        return floor(microtime(true) * 1000);
    }

    /**
     * 返回当前工作机器ID
     *
     * @return int
     */
    public function getWorkerId()
    {
        return $this->macheineId;
    }

    /**
     * 返回当前序列号
     *
     * @return int
     */
    public function getSequence()
    {
        return $this->sequence;
    }

    /**
     * 等待下一个毫秒
     *
     * @return int
     */
    protected function tilNextMillis($lastTimestamp)
    {
        $timestamp = $this->getTimestamp();
        while ($timestamp <= $lastTimestamp) {
            $timestamp = $this->getTimestamp();
        }
        return $timestamp;
    }
    /**
     * 下一个序列号
     *
     * @return int
     */
    protected function nextSequence()
    {
        return $this->sequence++;
    }

    /**
     * 最大工作机器ID
     *
     * @return int
     */
    private function maxWorkerId()
    {
        return -1 ^ (-1 << self::WORKER_ID_BITS);
    }

    /**
     * 获取工作ID位数
     *
     * @return void
     */
    private function macheineIdShift()
    {
        return self::SEQUENCE_BITS;
    }

    private function timestampLeftShift()
    {
        return self::SEQUENCE_BITS + self::WORKER_ID_BITS;
    }

    private function sequenceMask()
    {
        return -1 ^ (-1 << self::SEQUENCE_BITS);
    }

    /**
     * 设置工作机器ID
     *
     * @param int $macheineId
     * @throws \Exception
     */
    private function setWorkerId($macheineId)
    {
        if ($macheineId > $this->maxWorkerId() || $macheineId < 0) {
            throw new Exception('工作机器ID不能大于' . $this->maxWorkerId());
        }

        if ($macheineId < 0) {
            throw new Exception('工作机器ID不能小于0');
        }

        $this->macheineId = $macheineId;
    }
}
