// Copyright (C) 2016, 2017, 2018, 2019  Stefan Vargyas
// 
// This file is part of Json-Type.
// 
// Json-Type is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
// 
// Json-Type is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU General Public License for more details.
// 
// You should have received a copy of the GNU General Public License
// along with Json-Type.  If not, see <http://www.gnu.org/licenses/>.

#ifndef __FNV_HASH_H
#define __FNV_HASH_H

#ifndef FNV_HASH_BITS
#define FNV_HASH_BITS 32
#endif

#if FNV_HASH_BITS == 32
typedef uint32_t fnv_hash_t;
#define FNV_HASH_PREC 8
#define FNV_HASH_ZFMT "%08" PRIx32
#define FNV_HASH_MAX UINT32_MAX
#elif FNV_HASH_BITS == 64
typedef uint64_t fnv_hash_t;
#define FNV_HASH_PREC 16
#define FNV_HASH_ZFMT "%016" PRIx64
#define FNV_HASH_MAX UINT64_MAX
#else
#error FNV_HASH_BITS is neither 32 nor 64
#endif

#if FNV_HASH_MAX == UINT_MAX
#define FNV(x) x ## U
#elif FNV_HASH_MAX == ULONG_MAX
#define FNV(x) x ## UL
#elif FNV_HASH_MAX == ULLONG_MAX
#define FNV(x) x ## ULL
#else
#error fnv_hash_t is neither \
    unsigned int nor \
    unsigned long nor \
    unsigned long long
#endif

// http://www.isthe.com/chongo/tech/comp/fnv/index.html
// FNV Hash

#if FNV_HASH_BITS == 32
#define FNV_HASH_PRIME        FNV(16777619)
#define FNV_HASH_OFFSET_BASIS FNV(2166136261)
#elif FNV_HASH_BITS == 64
#define FNV_HASH_PRIME        FNV(1099511628211)
#define FNV_HASH_OFFSET_BASIS FNV(14695981039346656037)
#endif

#define FNV_HASH_INIT_(h) \
        h = FNV_HASH_OFFSET_BASIS

#define FNV_HASH_ADD_(h, c)  \
    (                        \
        h *= FNV_HASH_PRIME, \
        h ^= c               \
    )

#define FNV_HASH_INIT(h)                  \
    (                                     \
        STATIC(TYPEOF_IS(h, fnv_hash_t)), \
        FNV_HASH_INIT_(h)                 \
    )

#define FNV_HASH_ADD(h, c)                \
    (                                     \
        STATIC(TYPEOF_IS(h, fnv_hash_t)), \
        STATIC(TYPEOF_IS(c, uchar_t)),    \
        FNV_HASH_ADD_(h, c)               \
    )

#define FNV_HASH_DIGEST(s)              \
    ({                                  \
        fnv_hash_t __r;                 \
        const uchar_t *__p;             \
        STATIC(TYPEOF_IS_UCHAR_PTR(s)); \
        FNV_HASH_INIT_(__r);            \
        for (__p = (s); *__p; __p ++)   \
             FNV_HASH_ADD_(__r, *__p);  \
        __r;                            \
    })

#endif // __FNV_HASH_H


