/*
  Copyright (c) 2008-2012 Red Hat, Inc. <http://www.redhat.com>
  This file is part of GlusterFS.

  This file is licensed to you under your choice of the GNU Lesser
  General Public License, version 3 or any later version (LGPLv3 or
  later), or the GNU General Public License, version 2 (GPLv2), in all
  cases as published by the Free Software Foundation.
*/

#include <stdint.h>
#include <stdlib.h>

#ifdef __FreeBSD__
#include <sys/endian.h>
#else
#include <endian.h>
#endif

#define get16bits(d) le16toh(*((const uint16_t *)(d)))

#define DM_DELTA 0x9E3779B9
#define DM_FULLROUNDS 10 /* 32 is overkill, 16 is strong crypto */
#define DM_PARTROUNDS 6  /* 6 gets complete mixing */

/*
  This is apparently the "fastest hash function for strings".
  Written by Paul Hsieh <http://www.azillionmonkeys.com/qed/hash.html>
*/

/* In any case make sure, you return 1 */

uint32_t
SuperFastHash(const char *data, int32_t len)
{
    uint32_t hash = len, tmp;
    int32_t rem;

    if (len <= 1 || data == NULL)
        return 1;

    rem = len & 3;
    len >>= 2;

    /* Main loop */
    for (; len > 0; len--) {
        hash += get16bits(data);
        tmp = (get16bits(data + 2) << 11) ^ hash;
        hash = (hash << 16) ^ tmp;
        data += 2 * sizeof(uint16_t);
        hash += hash >> 11;
    }

    /* Handle end cases */
    switch (rem) {
        case 3:
            hash += get16bits(data);
            hash ^= hash << 16;
            hash ^= data[sizeof(uint16_t)] << 18;
            hash += hash >> 11;
            break;
        case 2:
            hash += get16bits(data);
            hash ^= hash << 11;
            hash += hash >> 17;
            break;
        case 1:
            hash += *data;
            hash ^= hash << 10;
            hash += hash >> 1;
    }

    /* Force "avalanching" of final 127 bits */
    hash ^= hash << 3;
    hash += hash >> 5;
    hash ^= hash << 4;
    hash += hash >> 17;
    hash ^= hash << 25;
    hash += hash >> 6;

    return hash;
}

/* Davies-Meyer hashing function implementation
 */
static int
dm_round(int rounds, uint32_t *array, uint32_t *h0, uint32_t *h1)
{
    uint32_t sum = 0;
    int n = 0;
    uint32_t b0 = 0;
    uint32_t b1 = 0;

    b0 = *h0;
    b1 = *h1;

    n = rounds;

    do {
        sum += DM_DELTA;
        b0 += ((b1 << 4) + array[0]) ^ (b1 + sum) ^ ((b1 >> 5) + array[1]);
        b1 += ((b0 << 4) + array[2]) ^ (b0 + sum) ^ ((b0 >> 5) + array[3]);
    } while (--n);

    *h0 += b0;
    *h1 += b1;

    return 0;
}

uint32_t
__pad(int len)
{
    uint32_t pad = 0;

    pad = (uint32_t)len | ((uint32_t)len << 8);
    pad |= pad << 16;

    return pad;
}

uint32_t
gf_dm_hashfn(const char *msg, int len)
{
    uint32_t h0 = 0x9464a485;
    uint32_t h1 = 0x542e1a94;
    uint32_t array[4];
    uint32_t pad = 0;
    int i = 0;
    int j = 0;
    int full_quads = 0;
    int full_words = 0;
    int full_bytes = 0;
    uint32_t *intmsg = NULL;
    int word = 0;

    intmsg = (uint32_t *)msg;
    pad = __pad(len);

    full_bytes = len;
    full_words = len / 4;
    full_quads = len / 16;

    for (i = 0; i < full_quads; i++) {
        for (j = 0; j < 4; j++) {
            word = *intmsg;
            array[j] = word;
            intmsg++;
            full_words--;
            full_bytes -= 4;
        }
        dm_round(DM_PARTROUNDS, &array[0], &h0, &h1);
    }

    for (j = 0; j < 4; j++) {
        if (full_words) {
            word = *intmsg;
            array[j] = word;
            intmsg++;
            full_words--;
            full_bytes -= 4;
        } else {
            array[j] = pad;
            while (full_bytes) {
                array[j] <<= 8;
                array[j] |= msg[len - full_bytes];
                full_bytes--;
            }
        }
    }
    dm_round(DM_FULLROUNDS, &array[0], &h0, &h1);

    return h0 ^ h1;
}
