// Copyright 2012-2020 David Robillard <d@drobilla.net>
// SPDX-License-Identifier: ISC

#include "zix/digest.h"

#ifdef __SSE4_2__
#  include <smmintrin.h>
#endif

#include <assert.h>
#include <stdint.h>

#ifdef __SSE4_2__

// SSE 4.2 CRC32

uint32_t
zix_digest_start(void)
{
  return 1;
}

uint32_t
zix_digest_add(uint32_t hash, const void* const buf, const size_t len)
{
  const uint8_t* str = (const uint8_t*)buf;

#  ifdef __x86_64__
  for (size_t i = 0; i < (len / sizeof(uint64_t)); ++i) {
    hash = (uint32_t)_mm_crc32_u64(hash, *(const uint64_t*)str);
    str += sizeof(uint64_t);
  }
  if (len & sizeof(uint32_t)) {
    hash = _mm_crc32_u32(hash, *(const uint32_t*)str);
    str += sizeof(uint32_t);
  }
#  else
  for (size_t i = 0; i < (len / sizeof(uint32_t)); ++i) {
    hash = _mm_crc32_u32(hash, *(const uint32_t*)str);
    str += sizeof(uint32_t);
  }
#  endif
  if (len & sizeof(uint16_t)) {
    hash = _mm_crc32_u16(hash, *(const uint16_t*)str);
    str += sizeof(uint16_t);
  }
  if (len & sizeof(uint8_t)) {
    hash = _mm_crc32_u8(hash, *(const uint8_t*)str);
  }

  return hash;
}

uint32_t
zix_digest_add_64(uint32_t hash, const void* const buf, const size_t len)
{
  assert((uintptr_t)buf % sizeof(uint64_t) == 0);
  assert(len % sizeof(uint64_t) == 0);

#  ifdef __x86_64__
  const uint64_t* ptr = (const uint64_t*)buf;

  for (size_t i = 0; i < (len / sizeof(uint64_t)); ++i) {
    hash = (uint32_t)_mm_crc32_u64(hash, *ptr);
    ++ptr;
  }

  return hash;
#  else
  const uint32_t* ptr = (const uint32_t*)buf;

  for (size_t i = 0; i < (len / sizeof(uint32_t)); ++i) {
    hash = _mm_crc32_u32(hash, *ptr);
    ++ptr;
  }

  return hash;
#  endif
}

uint32_t
zix_digest_add_ptr(const uint32_t hash, const void* const ptr)
{
#  ifdef __x86_64__
  return (uint32_t)_mm_crc32_u64(hash, (uintptr_t)ptr);
#  else
  return _mm_crc32_u32(hash, (uintptr_t)ptr);
#  endif
}

#else

// Classic DJB hash

uint32_t
zix_digest_start(void)
{
  return 5381;
}

uint32_t
zix_digest_add(uint32_t hash, const void* const buf, const size_t len)
{
  const uint8_t* str = (const uint8_t*)buf;

  for (size_t i = 0; i < len; ++i) {
    hash = (hash << 5u) + hash + str[i];
  }

  return hash;
}

uint32_t
zix_digest_add_64(uint32_t hash, const void* const buf, const size_t len)
{
  assert((uintptr_t)buf % sizeof(uint64_t) == 0);
  assert(len % sizeof(uint64_t) == 0);

  return zix_digest_add(hash, buf, len);
}

uint32_t
zix_digest_add_ptr(const uint32_t hash, const void* const ptr)
{
  return zix_digest_add(hash, &ptr, sizeof(ptr));
}

#endif
