package cn.estsh.i3plus.pojo.base.codemaker;

import cn.estsh.i3plus.pojo.base.enumutil.CommonEnumUtil;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import sun.management.VMManagement;

import java.lang.management.ManagementFactory;
import java.lang.management.RuntimeMXBean;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.net.InetAddress;
import java.net.NetworkInterface;
import java.util.concurrent.ConcurrentHashMap;

/**
 * @Description : 使用SnowFlake，生成分布式ID
 * @Reference : SnowFlake可以保证：
 * 所有生成的id按时间趋势递增
 * 整个分布式系统内不会产生重复id（因为有datacenterId和workerId来做区分）
 * @Author : alwaysfrin
 * @CreateDate : 2018-09-11 16:35
 * @Modify:
 **/
public class SnowflakeIdMaker {

    public static final Logger LOGGER = LoggerFactory.getLogger(SnowflakeIdMaker.class);

    private static final ConcurrentHashMap<String,SnowflakeIdMaker> RAM_CACHE_MAP = new ConcurrentHashMap();

    private long workerId;
    private long datacenterId;
    private long sequence = 0L;

    /**
     * 禁止私自构建,防止ID重复
     */
    @Deprecated
    private SnowflakeIdMaker() {
        this.workerId = getWorkerMacId();
        this.datacenterId = getDatacenterPid();
        this.sequence = 0L;
    }

    /**
     * 禁止私自构建 防止 ID 生成重复
     * @param workerId      工作区ID
     * @param datacenterId  数据区ID
     */
    private SnowflakeIdMaker(long workerId, long datacenterId) {
        if (workerId > maxWorkerId || workerId < 0) {
            throw new IllegalArgumentException(String.format("worker Id can't be greater than %d or less than 0", maxWorkerId));
        }
        if (datacenterId > maxDatacenterId || datacenterId < 0) {
            throw new IllegalArgumentException(String.format("datacenter Id can't be greater than %d or less than 0", maxDatacenterId));
        }

        this.workerId = workerId;
        this.datacenterId = datacenterId;
    }

    /**
     * 获取雪花生成器
     * @param workerId 工作区ID
     * @param softType 产品类型
     * @return
     */
    public static SnowflakeIdMaker getSnowflakeIdMaker(long workerId, CommonEnumUtil.SOFT_TYPE softType) {
        if(softType == null){
            throw new IllegalArgumentException(String.format("The data warehouse soft type in the data center cannot be empty"));
        }

        return getSnowflakeIdMaker(workerId, softType.getSnowflakeId());
    }

    /**
     * 获取雪花生成器
     * @param workerId      工作区ID
     * @param datacenterId  数据仓ID
     * @return
     */
    public static SnowflakeIdMaker getSnowflakeIdMaker(long workerId, long datacenterId) {
        String cacheKey = workerId + "_" + datacenterId;
        SnowflakeIdMaker maker = RAM_CACHE_MAP.get(cacheKey);
        if (maker == null) {
            maker = new SnowflakeIdMaker(workerId, datacenterId);
            RAM_CACHE_MAP.put(cacheKey, maker);
        }
        return maker;
    }

    private long twepoch = 1288834974657L;

    private long workerIdBits = 5L;
    private long datacenterIdBits = 5L;
    private long maxWorkerId = -1L ^ (-1L << workerIdBits); //30
    private long maxDatacenterId = -1L ^ (-1L << datacenterIdBits);  //30
    private long sequenceBits = 12L;

    private long workerIdShift = sequenceBits;
    private long datacenterIdShift = sequenceBits + workerIdBits;
    private long timestampLeftShift = sequenceBits + workerIdBits + datacenterIdBits;
    private long sequenceMask = -1L ^ (-1L << sequenceBits);

    private long lastTimestamp = -1L;

    private long getWorkerId(){
        return workerId;
    }

    private long getDatacenterId(){
        return datacenterId;
    }

    public synchronized long nextId() {
        long timestamp = timeGen();

        if (timestamp < lastTimestamp) {
            System.err.printf("clock is moving backwards.  Rejecting requests until %d.", 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 = 0;
        }

        lastTimestamp = timestamp;
        return ((timestamp - twepoch) << timestampLeftShift) |
                (datacenterId << datacenterIdShift) |
                (workerId << workerIdShift) |
                sequence;
    }

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

    /**
     * 使用本机Mac 地址的HashCode 取摸 32
     * @return
     */
    private long getWorkerMacId() {
        long result;
        try {
            InetAddress ia;
            byte[] mac = null;
            //获取本地IP对象
            ia = InetAddress.getLocalHost();
            //获得网络接口对象（即网卡），并得到mac地址，mac地址存在于一个byte数组中。
            mac = NetworkInterface.getByInetAddress(ia).getHardwareAddress();
            //下面代码是把mac地址拼装成String
            StringBuffer sb = new StringBuffer();
            for (int i = 0; i < mac.length; i++) {
                if (i != 0) {
                    sb.append("-");
                }
                //mac[i] & 0xFF 是为了把byte转化为正整数
                String s = Integer.toHexString(mac[i] & 0xFF);
                sb.append(s.length() == 1 ? 0 + s : s);
            }

            //把字符串所有小写字母改为大写成为正规的mac地址并返回
            result = sb.toString().toUpperCase().hashCode();
        }catch (Exception e){
            e.printStackTrace();
            result = 31;
            LOGGER.error("[SnowflakeIdMaker] 获取设备ID错误：{}",e.getMessage());
        }

        result = result + timeGen();
        return result < 0 ? (0 - result) % 32 : result % 32;
    }

    private long getDatacenterPid() {
        long result;
        try {
            RuntimeMXBean runtime = ManagementFactory.getRuntimeMXBean();
            Field jvm = runtime.getClass().getDeclaredField("jvm");
            jvm.setAccessible(true);
            VMManagement mgmt = (VMManagement) jvm.get(runtime);
            Method pidMethod = mgmt.getClass().getDeclaredMethod("getProcessId");
            pidMethod.setAccessible(true);
            result = (Integer) pidMethod.invoke(mgmt);
        } catch (Exception e) {
            result = 31;
        }

        result = result + timeGen();
        return result < 0 ? (0 - result) % 32 : result % 32;
    }

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

    //---------------测试---------------
    public static void main(String[] args) {
        SnowflakeIdMaker worker = new SnowflakeIdMaker();
        Runnable testRun = new Runnable() {
            @Override
            public void run() {
                for (int i = 0; i < 10; i++) {
                    System.out.println(worker.nextId());
                }
            }
        };
        Thread thread1 = new Thread(testRun);
        thread1.start();
        Thread thread2 = new Thread(testRun);
        thread2.start();
        Thread thread3 = new Thread(testRun);
        thread3.start();
        Thread thread4 = new Thread(testRun);
        thread4.start();
        Thread thread5 = new Thread(testRun);
        thread5.start();

        for (int i = 0; i < 10; i++) {
            System.out.println(worker.nextId());
        }
    }
}
