#include "siphash.h"

#define ROTL(x, b) (ui64)(((x) << (b)) | ((x) >> (64 - (b))))

#define SIPROUND                    \
do {                                \
    v0 += v1; v1 = ROTL(v1, 13);    \
    v1 ^= v0; v0 = ROTL(v0, 32);    \
    v2 += v3; v3 = ROTL(v3, 16);    \
    v3 ^= v2;                       \
    v0 += v3; v3 = ROTL(v3, 21);    \
    v3 ^= v0;                       \
    v2 += v1; v1 = ROTL(v1, 17);    \
    v1 ^= v2; v2 = ROTL(v2, 32);    \
} while (0)

TSipHash::TSipHash(ui64 k0, ui64 k1) {
    /// Инициализируем состояние некоторыми случайными байтами и seed-ом.
    v0 = 0x736f6d6570736575ULL ^ k0;
    v1 = 0x646f72616e646f6dULL ^ k1;
    v2 = 0x6c7967656e657261ULL ^ k0;
    v3 = 0x7465646279746573ULL ^ k1;

    cnt = 0;
    current_word = 0;
}

void TSipHash::Finalize() {
    /// В последний свободный байт пишем остаток от деления длины на 256.
    current_bytes[7] = cnt;

    v3 ^= current_word;
    SIPROUND;
    SIPROUND;
    v0 ^= current_word;

    v2 ^= 0xff;
    SIPROUND;
    SIPROUND;
    SIPROUND;
    SIPROUND;
}

void TSipHash::Update(const char* data, ui64 size) {
    const char* end = data + size;

    /// Дообработаем остаток от предыдущего апдейта, если есть.
    if (cnt & 7) {
        while (cnt & 7 && data < end) {
            current_bytes[cnt & 7] = *data;
            ++data;
            ++cnt;
        }

        /// Если всё ещё не хватает байт до восьмибайтового слова.
        if (cnt & 7)
            return;

        v3 ^= current_word;
        SIPROUND;
        SIPROUND;
        v0 ^= current_word;
    }

    cnt += end - data;

    while (data + 8 <= end) {
        current_word = *reinterpret_cast<const ui64*>(data);

        v3 ^= current_word;
        SIPROUND;
        SIPROUND;
        v0 ^= current_word;

        data += 8;
    }

    /// Заполняем остаток, которого не хватает до восьмибайтового слова.
    current_word = 0;
    switch (end - data) {
        case 7:
            current_bytes[6] = data[6];
        case 6:
            current_bytes[5] = data[5];
        case 5:
            current_bytes[4] = data[4];
        case 4:
            current_bytes[3] = data[3];
        case 3:
            current_bytes[2] = data[2];
        case 2:
            current_bytes[1] = data[1];
        case 1:
            current_bytes[0] = data[0];
        case 0:
            break;
    }
}

#undef ROTL
#undef SIPROUND

void TSipHash::Get128(char* out) {
    Finalize();
    reinterpret_cast<ui64*>(out)[0] = v0 ^ v1;
    reinterpret_cast<ui64*>(out)[1] = v2 ^ v3;
}

void TSipHash::Get128(ui64& lo, ui64& hi) {
    Finalize();
    lo = v0 ^ v1;
    hi = v2 ^ v3;
}

ui64 TSipHash::Get64() {
    Finalize();
    return v0 ^ v1 ^ v2 ^ v3;
}
