/*
 * Copyright 2010-2024 JetBrains s.r.o. and Kotlin Programming Language contributors.
 * Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file.
 */

#include <string.h>
#include <stdio.h>
#include <stdarg.h>

#if KONAN_MACOSX || KONAN_IOS || KONAN_TVOS || KONAN_WATCHOS || KONAN_ANDROID
#include <stdlib.h>
#elif KONAN_LINUX
#include <unistd.h>
#include <sys/syscall.h>
#include <errno.h>
#elif KONAN_WINDOWS
#include <windows.h>
#include <bcrypt.h>
#include <ntdef.h>
#endif

#include "KString.h"
#include "Exceptions.h"
#include "Format.h"
#include "Memory.h"
#include "Natives.h"

namespace {

#if KONAN_LINUX || KONAN_WINDOWS
RUNTIME_NORETURN __attribute__((format(printf, 1, 2))) void throwReadingRandomBytesFailed(const char* format, ...) {
    va_list args;
    va_start(args, format);
    std::array<char, 128> buffer;
    kotlin::std_support::span<char> span(buffer);
    span = kotlin::VFormatToSpan(span, format, args);
    va_end(args);

    ObjHolder holder;
    CreateStringFromUtf8(buffer.data(), buffer.size() - span.size(), holder.slot());
    ThrowIllegalStateExceptionWithMessage(holder.obj());
}
#endif

}  // namespace


extern "C" {

// Mostly taken from mimalloc's random.c
void Kotlin_Uuid_getRandomBytes(KRef byteArray, KInt size) {
    kotlin::ThreadStateGuard guard(kotlin::ThreadState::kNative);
    ArrayHeader* array = byteArray->array();
    uint8_t* address = reinterpret_cast<uint8_t*>(ByteArrayAddressOfElementAt(array, 0));

#if KONAN_MACOSX || KONAN_IOS || KONAN_TVOS || KONAN_WATCHOS || KONAN_ANDROID
    arc4random_buf(address, size);
#elif KONAN_LINUX
    long count = 0;
    while (count < size) {
        long ret = syscall(SYS_getrandom, address + count, size - count, 0); // blocking
        if (ret >= 0) {
            count += ret;
        } else if (errno != EINTR) { // repeat if interrupted
            throwReadingRandomBytesFailed("getrandom returned a negative value: %ld, error: %s", ret, strerror(errno));
        }
    }
#elif KONAN_WINDOWS
    NTSTATUS status = BCryptGenRandom(NULL, (PUCHAR)address, (ULONG)size, BCRYPT_USE_SYSTEM_PREFERRED_RNG);
    if (!NT_SUCCESS(status)) {
        throwReadingRandomBytesFailed("Unexpected failure in random bytes generation: %ld", status);
    }
#else
#error "How to Kotlin_Uuid_getRandomBytes()?"
#endif
}

}  // extern "C"