package cn.quantgroup.xyqb.util;

/**
 * Created by hechao on 2017/8/28.
 */
public class SnowflakeIdentitySequencer implements IdentitySequencer {

    private static final long MAX_WORKER_ID = 0xFFFFFFFFFFL;

    private final static int bits = 63;

    private final static int SEQ_MAX_BITS = 8;

    private final static int SEQ_MIN_BITS = 2;

    private final static int timestampMinBits = 41;

    private final static int FLAKE_BITS = 8;

    private final long twepoch;

    private final static long START_DATE = 473356800000L;

    private final int sequenceBits;

    private volatile long lastTimestamp = -1L;

    private volatile long timestamp = -1L;

    private volatile long sequence = 0L;

    private final long sequenceMask;

    private final long workerId;

    public final static String TWEPOCH_XOR = "TWEPOCH_XOR";
    public final static String TWEPOCH_PLUS = "TWEPOCH_PLUS";

    public SnowflakeIdentitySequencer(long workerId, int sequenceBits, String oper) {
        if (workerId > MAX_WORKER_ID || workerId < 1) {
            throw new IllegalArgumentException(String.format("worker Id can't be greater than %d or less than 0", MAX_WORKER_ID, 1));
        }
        if (sequenceBits > SEQ_MAX_BITS || sequenceBits < 2) {
            throw new IllegalArgumentException(String.format("sequence bits can't be greater than %d or less than 0", SEQ_MAX_BITS, 2));
        }
        this.workerId = workerId;
        if (TWEPOCH_XOR.equals(oper)) {
            this.twepoch = START_DATE ^ workerId;
        } else if (TWEPOCH_PLUS.equals(oper)) {
            this.twepoch = START_DATE + workerId;
        } else {
            throw new IllegalArgumentException("operator use TWEPOCH_XOR or TWEPOCH_PLUS");
        }

        this.sequenceBits = sequenceBits;
        this.sequenceMask = -1 << sequenceBits ^ -1;
    }

    public SnowflakeIdentitySequencer(long workerId, int sequenceBits) {
        this(workerId, sequenceBits, TWEPOCH_XOR);
    }

    public SnowflakeIdentitySequencer(long workerId) {
        this(workerId, SEQ_MAX_BITS);
    }

    public synchronized long nextId(long flake) {
        timestamp = System.currentTimeMillis();
        if (timestamp < lastTimestamp) {
            throw new RuntimeException(String.format("Clock moved backwards.  Refusing to generate id for %d milliseconds", lastTimestamp - timestamp));
        }
        if (timestamp == lastTimestamp) {
            sequence = (sequence + 1) & sequenceMask;
            if (sequence == 0) {
                while (timestamp == lastTimestamp) {
                    timestamp = System.currentTimeMillis();
                }
            }
        }
        lastTimestamp = timestamp;
        return ((((timestamp - twepoch) << sequenceBits) | sequence) << FLAKE_BITS) | flake;
//        return ((timestamp - twepoch) << sequenceBits) | sequence;
    }

    public long nextId() {
        return nextId(0L);
    }

    @Override
    public long workerId() {
        return workerId;
    }

    @Override
    public String toString() {
        return "SnowflakeIdWorker{" +
                "sequence=" + sequence +
                ", twepoch=" + twepoch +
                ", workerId=" + workerId +
                '}';
    }

    public static void main(String[] args) {
        SnowflakeIdentitySequencer snowflakeIdWorker = new SnowflakeIdentitySequencer(255255255255L ^ 8000, 8);
//        SnowflakeIdentitySequencer snowflakeIdWorker = new SnowflakeIdentitySequencer(1099511627775L, 8, TWEPOCH_PLUS);
//        SnowflakeIdWorker snowflakeIdWorker = new SnowflakeIdWorker(255255255255L, 16, 8);
//        SnowflakeIdWorker snowflakeIdWorker = new SnowflakeIdWorker(255255255255L, 16, 8);
//        SnowflakeIdWorker snowflakeIdWorker = new SnowflakeIdWorker(17202801560208L);

        Long nextId = snowflakeIdWorker.nextId(245);
        System.out.println(nextId);
//        for(int i=0; i<300;i++){
//            System.out.println(i+":"+(i&255));
//        }

//        for (int i = 0; i < 100; i++) {
//            new Thread(new Running(snowflakeIdWorker)).start();
//        }

//        System.out.println(73361139375145112L ^ (73361139375145112L >> 8 << 8));


//        System.out.println((nextId >> 8 << 8) ^ nextId);
//        for (int i = 0; i < 10000; i++) {
//            System.out.println(snowflakeIdWorker.nextId());
//        }
    }


    public static class Running implements Runnable {
        SnowflakeIdentitySequencer sequencer;

        public Running(SnowflakeIdentitySequencer sequencer) {
            this.sequencer = sequencer;
        }

        @Override
        public void run() {
            System.out.println(this.sequencer.nextId(255));
        }
    }

}
