/*
 * This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/.
 *
 * Copyright 2020, Danny Robson <danny@nerdcruft.net>
 *
 * Derived from the CC0 reference implementation by:
 *  Jean-Philippe Aumasson <jeanphilippe.aumasson@gmail.com>
 */

#pragma once

#include "../std.hpp"

#include <type_traits>
#include <span>


namespace cruft::hash {
    template <
        typename ResultT,
        int CRoundsV,
        int DRoundsV,
        typename = std::enable_if_t<std::is_same_v<ResultT, u32> || std::is_same_v<ResultT, u64>>
    >
    struct halfsiphash {
        u32 rotl (u32 x, u32 b)
        {
            return ((x) << (b)) | ((x) >> (32 - (b)));
        }

        u32 v0, v1, v2, v3;

        constexpr halfsiphash (u32 k0, u32 k1)
        {
            v0 =          0 ^ k0;
            v1 =          0 ^ k1;
            v2 = 0x6c796765 ^ k0;
            v3 = 0x74656462 ^ k1;

            if constexpr (std::is_same_v<ResultT, u64>)
                v1 ^= 0xee;
        }

        constexpr void round (void)
        {
            v0 += v1;
            v1 = rotl (v1, 5);
            v1 ^= v0;
            v0 = rotl (v0, 16);
            v2 += v3;
            v3 = rotl (v3, 8);
            v3 ^= v2;
            v0 += v3;
            v3 = rotl (v3, 7);
            v3 ^= v0;
            v2 += v1;
            v1 = rotl (v1, 13);
            v1 ^= v2;
            v2 = rotl (v2, 16);
        }

        constexpr void put (u32 word)
        {
            v3 ^= word;
            for (int i = 0; i < CRoundsV; ++i)
                round ();
            v0 ^= word;
        }

        constexpr ResultT
        operator() (
            std::span<u32 const> in
        ) {
            for (auto const &m: in)
                put (m);

            return finish (u32 (in.size ()));
        }

        template <typename ...ArgsT>
        requires (std::is_same_v<std::remove_cvref_t<ArgsT>, u32> && ...)
        constexpr ResultT
        operator() (ArgsT &&...vals)
        {
            (put (vals), ...);
            return finish (sizeof ...(vals));
        }

        constexpr ResultT
        finish (u32 count)
        {
            u32 const b = u32(count * sizeof (u32)) << 24;

            v3 ^= b;

            for (int i = 0; i < CRoundsV; ++i)
                round ();

            v0 ^= b;

            if (std::is_same_v<ResultT,u64>)
                v2 ^= 0xee;
            else
                v2 ^= 0xff;

            for (int i = 0; i < DRoundsV; ++i)
                round ();

            ResultT res = v1 ^ v3;

            if constexpr (std::is_same_v<ResultT, u32>)
                return res;

            v1 ^= 0xdd;

            for (int i = 0; i < DRoundsV; ++i)
                round ();

            res |= u64(v1 ^ v3) << 32;

            return res;
        }
    };


    template <
        int CRoundsV,
        int DRoundsV,
        typename ...ValuesT
    >
    requires (std::is_same_v<std::remove_cvref_t<ValuesT>, u32> && ...)
    inline constexpr
    u32 halfsipmix (u32 key0, u32 key1, ValuesT &&...vals)
    {
        return halfsiphash<u32, 2,4> (key0, key1) (vals...);
    }


    template <typename ...ValueT>
    decltype(auto)
    halfsipmix24 (ValueT &&...vals)
    {
        return halfsipmix<2, 4> (vals...);
    }
};