/**

Algorithm Info:
https://131002.net/siphash/

Based off of implementation:
https://github.com/floodyberry/siphash

 */

#include "tscore/HashSip.h"
#include <cstring>

using namespace std;

#define SIP_BLOCK_SIZE 8

#define ROTL64(a, b) (((a) << (b)) | ((a) >> (64 - b)))

#define U8TO64_LE(p) *(const uint64_t *)(p)

#define SIPCOMPRESS(x0, x1, x2, x3) \
  x0 += x1;                         \
  x2 += x3;                         \
  x1  = ROTL64(x1, 13);             \
  x3  = ROTL64(x3, 16);             \
  x1 ^= x0;                         \
  x3 ^= x2;                         \
  x0  = ROTL64(x0, 32);             \
  x2 += x1;                         \
  x0 += x3;                         \
  x1  = ROTL64(x1, 17);             \
  x3  = ROTL64(x3, 21);             \
  x1 ^= x2;                         \
  x3 ^= x0;                         \
  x2  = ROTL64(x2, 32);

ATSHash64Sip24::ATSHash64Sip24()
{
  this->clear();
}

ATSHash64Sip24::ATSHash64Sip24(const unsigned char key[16]) : k0(U8TO64_LE(key)), k1(U8TO64_LE(key + sizeof(k0)))
{
  this->clear();
}

ATSHash64Sip24::ATSHash64Sip24(uint64_t key0, uint64_t key1) : k0(key0), k1(key1)
{
  this->clear();
}

void
ATSHash64Sip24::update(const void *data, size_t len)
{
  size_t         i, blocks;
  unsigned char *m;
  uint64_t       mi;
  uint8_t        block_off = 0;

  if (!finalized) {
    m          = (unsigned char *)data;
    total_len += len;

    if (len + block_buffer_len < SIP_BLOCK_SIZE) {
      memcpy(block_buffer + block_buffer_len, m, len);
      block_buffer_len += len;
    } else {
      if (block_buffer_len > 0) {
        block_off = SIP_BLOCK_SIZE - block_buffer_len;
        memcpy(block_buffer + block_buffer_len, m, block_off);

        mi  = U8TO64_LE(block_buffer);
        v3 ^= mi;
        SIPCOMPRESS(v0, v1, v2, v3);
        SIPCOMPRESS(v0, v1, v2, v3);
        v0 ^= mi;
      }

      for (i = block_off, blocks = ((len - block_off) & ~(SIP_BLOCK_SIZE - 1)); i < blocks; i += SIP_BLOCK_SIZE) {
        mi  = U8TO64_LE(m + i);
        v3 ^= mi;
        SIPCOMPRESS(v0, v1, v2, v3);
        SIPCOMPRESS(v0, v1, v2, v3);
        v0 ^= mi;
      }

      block_buffer_len = (len - block_off) & (SIP_BLOCK_SIZE - 1);
      memcpy(block_buffer, m + block_off + blocks, block_buffer_len);
    }
  }
}

void
ATSHash64Sip24::final()
{
  uint64_t last7;
  int      i;

  if (!finalized) {
    last7 = static_cast<uint64_t>(total_len & 0xff) << 56;

    for (i = block_buffer_len - 1; i >= 0; i--) {
      last7 |= static_cast<uint64_t>(block_buffer[i]) << (i * 8);
    }

    v3 ^= last7;
    SIPCOMPRESS(v0, v1, v2, v3);
    SIPCOMPRESS(v0, v1, v2, v3);
    v0 ^= last7;
    v2 ^= 0xff;
    SIPCOMPRESS(v0, v1, v2, v3);
    SIPCOMPRESS(v0, v1, v2, v3);
    SIPCOMPRESS(v0, v1, v2, v3);
    SIPCOMPRESS(v0, v1, v2, v3);
    hfinal    = v0 ^ v1 ^ v2 ^ v3;
    finalized = true;
  }
}

uint64_t
ATSHash64Sip24::get() const
{
  if (finalized) {
    return hfinal;
  } else {
    return 0;
  }
}

void
ATSHash64Sip24::clear()
{
  v0               = k0 ^ 0x736f6d6570736575ull;
  v1               = k1 ^ 0x646f72616e646f6dull;
  v2               = k0 ^ 0x6c7967656e657261ull;
  v3               = k1 ^ 0x7465646279746573ull;
  finalized        = false;
  total_len        = 0;
  block_buffer_len = 0;
}
