package com.base.biz.config;


import java.math.BigInteger;
import java.net.InetAddress;
import java.net.UnknownHostException;


public class IdGenerator {

    /**
     * 初始时间戳，一经定义，不可修改
     */
    private final long twepoch = 1534435200000L;

    /**
     * 机器码长度
     */
    private final long workerIdBits = 8L;

    /**
     * 最大机器数量
     */
    private final long maxWorkerId = -1L ^ (-1L << workerIdBits);

    /**
     * 机器数量左移偏量
     */
    private final long workerIdShift;

    /**
     * 时间戳量左移偏量
     */
    private final long timestampLeftShift;

    private final long sequenceMask;

    private static IdGenerator idGenerator;

    /**
     * 二进制转义对应字符
     */
    private final String[] charMappingConstant = {
            "1", "2", "3", "4", "5", "6", "7", "8", "9", "0",
            "a", "b", "c", "d", "e", "f", "g", "h", "i", "j",
            "k", "l", "m", "n", "o", "p", "q", "r", "s", "t",
            "u", "v", "w", "x", "y", "z"};

    private long workerId;
    private long sequence;
    private long lastTimestamp = -1L;

    /**
     * 每毫秒最大产生31条数据，默认(2^6-1)，若并发量不大可选带参构造函数
     */
    public IdGenerator(){
        
        String hostAddress;
        try {
            hostAddress = InetAddress.getLocalHost().getHostAddress();
        } catch (UnknownHostException e) {
            throw new RuntimeException(e);
        }
        Long workerId = Long.parseLong(hostAddress.substring(hostAddress.lastIndexOf(".") + 1, hostAddress.length()));
        if (workerId > maxWorkerId || workerId < 0) {
            throw new IllegalArgumentException(String.format("worker Id can't be greater than %d or less than 0", workerId));
        }

        long sequenceBits = 6L;
        this.workerId = workerId;
        this.workerIdShift = sequenceBits;
        this.timestampLeftShift = sequenceBits + workerIdBits;
        this.sequenceMask = -1L ^ (-1L << sequenceBits);
    }

    /**
     * @param sequenceBits
     * @description 每毫秒最大产生2的(sequenceBits - 1)次方条数据，若超出须等待至下一毫秒
     */
    public IdGenerator(long sequenceBits) throws UnknownHostException {
        String hostAddress = InetAddress.getLocalHost().getHostAddress();
        Long workerId = Long.parseLong(hostAddress.substring(hostAddress.lastIndexOf(".") + 1, hostAddress.length()));
        if (workerId > maxWorkerId || workerId < 0) {
            throw new IllegalArgumentException(String.format("worker Id can't be greater than %d or less than 0", workerId));
        }

        if (sequenceBits > 6L) {
            sequenceBits = 6L;
        }
        this.workerId = workerId;
        this.workerIdShift = sequenceBits;
        this.timestampLeftShift = sequenceBits + workerIdBits;
        this.sequenceMask = -1L ^ (-1L << sequenceBits);
    }

    public synchronized String nextId() {
        return convert(Long.toBinaryString(nextLong()));
    }

    public static synchronized long getId(){
        if(idGenerator == null){
            idGenerator = new IdGenerator();
        }
        return idGenerator.nextLong();
    }

    public static synchronized String getStrId(){
        return String.valueOf(IdGenerator.getId());
    }

    public synchronized long nextLong() {
        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) & sequenceMask;
            if (sequence == 0) {
                timestamp = tilNextMillis(lastTimestamp);
            }
        } else {
            sequence = 0L;
        }

        lastTimestamp = timestamp;

        //二进制ID，通过拼接时间，机器码，时序码生成
        long id = ((timestamp - twepoch) << timestampLeftShift) | (workerId << workerIdShift) | sequence;
        return id;
    }

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

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

    /**
     * 二进制ID转义
     * @param str
     * @return
     */
    private String convert(final String str) {
        StringBuffer sb = new StringBuffer();

        int times = (int) Math.ceil((double) str.length() / 5);

        for (int i = 0; i < times; i++) {
            int end;
            if (str.length() / 5 > i) {
                end = (i + 1) * 5;
            } else {
                end = str.length();
            }
            String binarySource = str.substring(i * 5, end);
            BigInteger bi = new BigInteger(binarySource, 2);
            sb.append(charMappingConstant[Integer.parseInt(bi.toString())]);
        }

        return sb.toString();
    }

}

