# SPDX-FileCopyrightText: Daniele Nicolodi <daniele@grinta.net>
# SPDX-License-Identifier: Apache-2.0 OR LGPL-2.1-or-later
# cython: language_level=3, boundscheck=False, freethreading_compatible=True

from libc.stdint cimport uint64_t, int64_t, uint8_t
from libc.stdio cimport snprintf
from libc.string cimport memcpy, memset
from cpython cimport Py_buffer, PyObject_GetBuffer, PyBUF_SIMPLE, PyBuffer_Release, PyUnicode_Check, PyObject_CheckBuffer
from cpython.conversion cimport PyOS_snprintf

cdef extern from "c-siphash.h":
    ctypedef struct CSipHash:
        pass
    cdef void c_siphash_init(CSipHash *state, const uint8_t seed[16])
    cdef void c_siphash_append_13(CSipHash *state, const uint8_t *bytes, size_t n_bytes)
    cdef void c_siphash_append_24(CSipHash *state, const uint8_t *bytes, size_t n_bytes)
    cdef uint64_t c_siphash_finalize_13(CSipHash *state)
    cdef uint64_t c_siphash_finalize_24(CSipHash *state)


cdef bytes uint64le(uint64_t v):
    cdef uint8_t[8] r = [
        <uint8_t>(v >>  0),
        <uint8_t>(v >>  8),
        <uint8_t>(v >> 16),
        <uint8_t>(v >> 24),
        <uint8_t>(v >> 32),
        <uint8_t>(v >> 40),
        <uint8_t>(v >> 48),
        <uint8_t>(v >> 56),
    ]
    return <bytes>r[:8]


cdef str hexlify(uint64_t v):
    cdef char string[17]
    PyOS_snprintf(
        string, sizeof(string),
        '%02x%02x%02x%02x%02x%02x%02x%02x',
        <uint8_t>(v >>  0),
        <uint8_t>(v >>  8),
        <uint8_t>(v >> 16),
        <uint8_t>(v >> 24),
        <uint8_t>(v >> 32),
        <uint8_t>(v >> 40),
        <uint8_t>(v >> 48),
        <uint8_t>(v >> 56))
    return string[:16].decode('ascii')


{{for variant in "13", "24"}}

cdef class siphash{{variant}}:
    cdef CSipHash state

    cdef readonly int digest_size
    cdef readonly int block_size
    cdef readonly str name

    def __cinit__(self):
        self.digest_size = 8
        self.block_size = 8
        self.name = 'siphash{{variant}}'

    def __init__(self, data=b'', /, *, key=b''):
        """Return a new Sip Hash {{variant}} hash object."""
        cdef const uint8_t[::1] keybytes = memoryview(key).cast('B')
        if keybytes.shape[0] > 16:
            raise ValueError("maximum key length is 16 bytes")
        cdef uint8_t padded[16]
        memset(padded, 0, sizeof(padded))
        memcpy(padded, &keybytes[0], keybytes.shape[0])
        c_siphash_init(&self.state, padded)
        cdef const uint8_t[::1] databytes = memoryview(data).cast('B')
        c_siphash_append_{{variant}}(&self.state, &databytes[0], databytes.shape[0])

    def update(self, data=b''):
        """Update this hash object's state with the provided data."""
        cdef const uint8_t[::1] databytes = memoryview(data).cast('B')
        c_siphash_append_{{variant}}(&self.state, &databytes[0], databytes.shape[0])

    def digest(self):
        """Return the digest value as a bytes object."""
        cdef CSipHash state
        memcpy(&state, &self.state, sizeof(state))
        cdef uint64_t hash = c_siphash_finalize_{{variant}}(&state)
        return uint64le(hash)

    def hexdigest(self):
        """Return the digest value as a string of hexadecimal digits."""
        cdef CSipHash state
        memcpy(&state, &self.state, sizeof(state))
        cdef uint64_t hash = c_siphash_finalize_{{variant}}(&state)
        return hexlify(hash)

    def intdigest(self):
        """Return the digest calue as a signed 64bit integer."""
        cdef CSipHash state
        memcpy(&state, &self.state, sizeof(state))
        cdef uint64_t hash = c_siphash_finalize_{{variant}}(&state)
        return <int64_t>hash

    def copy(self):
        """Return a copy of the hash object."""
        cdef siphash{{variant}} r = siphash{{variant}}.__new__()
        memcpy(&r.state, &self.state, sizeof(r.state))
        return r

{{endfor}}
