package ru.yandex.webmaster3.storage.util;

import com.google.common.hash.HashFunction;
import com.google.common.hash.Hashing;

import java.nio.ByteBuffer;
import java.util.Comparator;
import java.util.UUID;

/**
 * @author avhaliullin
 */
public class UUIDUtil {
    private static long transformMostSigForOrdering(long msb) {
        return (msb & 0xFFFFL) << 48
                | ((msb >>> 16) & 0x0FFFFL) << 32
                | msb >>> 32 | ((msb & 0xF000L) << 48);
    }

    // Конвертируем в байты так, чтобы type-1 UUID'ы можно было сортировать по timestamp'у.
    // В type-1 UUID'е most significant часть состоит из 4-битной версии и 60-битного timestamp. Layout у этого дела вот такой:
    // ts{32 младших} ts{16 средних} v{4} ts{12 старших}
    // Для сортировки в БД переставляем все это вот так:
    // v{4} ts{12 старших} ts{16 средних} ts{32 младших}
    public static UUID bytesToUUID(byte[] bytesArr) {
        if (bytesArr == null) {
            return null;
        }
        ByteBuffer bytes = ByteBuffer.wrap(bytesArr);
        long transformed = bytes.getLong(bytes.position());
        long mostSig = 0L;
        mostSig |= (0x00000000ffffffffL & transformed) << 32;
        mostSig |= (0x0000ffff00000000L & transformed) >>> 16;
        mostSig |= (0xffff000000000000L & transformed) >>> 48;
        return new UUID(mostSig, bytes.getLong(bytes.position() + 8));
    }

    public static byte[] uuidToBytes(UUID uuid) {
        if (uuid == null) {
            return null;
        }
        ByteBuffer bb = ByteBuffer.allocate(16);

        long mostSig = uuid.getMostSignificantBits();
        long transformed = transformMostSigForOrdering(mostSig);

        bb.putLong(0, transformed);
        bb.putLong(8, uuid.getLeastSignificantBits());
        return bb.array();
    }

    /**
     * Преобразование над LSB, исполняемое в кассандре перед сравнением
     */
    private static long transformLSB(long lsb) {
        return lsb ^ 0x0080808080808080L;
    }

    /**
     * Сортирует timeuuid'ы по возрастанию timestamp, порядок сортировки соотвтетствует кассандравскому.
     * Не-timeuuid'ы сортируются каким-то бессмысленным но детерминированным образом.
     * uuid'ы разных версий сортируются в первую очередь по версии
     */
    public static final Comparator<UUID> UUID_COMPARATOR = (o1, o2) -> {
        long o1MostSig = transformMostSigForOrdering(o1.getMostSignificantBits());
        long o2MostSig = transformMostSigForOrdering(o2.getMostSignificantBits());
        int res = Long.compare(o1MostSig, o2MostSig);
        if (res == 0) {
            return Long.compare(transformLSB(o1.getLeastSignificantBits()), transformLSB(o2.getLeastSignificantBits()));
        } else {
            return res;
        }
    };

    private static final HashFunction MUR_MUR = Hashing.murmur3_32();

    public static int uuidHash(UUID uuid) {
        return MUR_MUR.newHasher()
                .putLong(uuid.getMostSignificantBits())
                .putLong(uuid.getLeastSignificantBits())
                .hash()
                .asInt();
    }
}
