#include "Support.h"

// Windows' existing hashing service is used only by the offline evidence tool.
// No dependency, hashing code, or Windows API call is added to the audio path.
#include <Windows.h>
#include <bcrypt.h>
#pragma comment (lib, "bcrypt.lib")

namespace deeppluck::benchmark
{
namespace
{
struct Hash
{
    BCRYPT_ALG_HANDLE algorithm = nullptr;
    BCRYPT_HASH_HANDLE handle = nullptr;
    Hash()
    {
        require (BCryptOpenAlgorithmProvider (&algorithm, BCRYPT_SHA256_ALGORITHM, nullptr, 0) >= 0,
                 "Cannot open SHA-256 provider");
        if (BCryptCreateHash (algorithm, &handle, nullptr, 0, nullptr, 0, 0) < 0)
        {
            BCryptCloseAlgorithmProvider (algorithm, 0);
            throw std::runtime_error ("Cannot create SHA-256");
        }
    }
    ~Hash()
    {
        if (handle != nullptr) BCryptDestroyHash (handle);
        if (algorithm != nullptr) BCryptCloseAlgorithmProvider (algorithm, 0);
    }
    void add (const void* data, size_t bytes)
    {
        require (bytes <= static_cast<size_t> (ULONG_MAX), "Hash chunk too large");
        require (BCryptHashData (handle, const_cast<PUCHAR> (static_cast<const UCHAR*> (data)),
                                static_cast<ULONG> (bytes), 0) >= 0, "Cannot hash data");
    }
    juce::String finish()
    {
        std::array<UCHAR, 32> digest {};
        require (BCryptFinishHash (handle, digest.data(), static_cast<ULONG> (digest.size()), 0) >= 0,
                 "Cannot finish SHA-256");
        return juce::String::toHexString (digest.data(), static_cast<int> (digest.size()), 0);
    }
};
}
juce::String sha256 (const void* data, size_t bytes)
{
    Hash hash;
    hash.add (data, bytes);
    return hash.finish();
}
juce::String fileSha256 (const juce::File& file)
{
    juce::FileInputStream stream (file);
    require (stream.openedOk(), "Cannot hash missing file");
    Hash hash;
    std::array<char, 65536> buffer {};
    while (! stream.isExhausted())
    {
        const auto count = stream.read (buffer.data(), static_cast<int> (buffer.size()));
        require (count > 0, "Hash input read failed");
        hash.add (buffer.data(), static_cast<size_t> (count));
    }
    return hash.finish();
}
}
