#pragma once

/*
* Some utility functions used for casting between types.
* Not a part of RandomX algorithm.
*/

#include <bit>
#include <concepts>
#include <numeric>
#include <span>
#include <utility>
#include <vector>

#include "aliases.hpp"

// Casts one const_span to another with different type and proportionally different size.
template<typename T, size_t Size = std::numeric_limits<size_t>::max(), typename Val, size_t Input_Size = std::numeric_limits<size_t>::max()>
[[nodiscard]] constexpr const_span<T, Size> span_cast(const_span<Val, Input_Size> v) noexcept {
    static_assert(Size * sizeof(T) == Input_Size * sizeof(Val));
    return const_span<T, Size>(reinterpret_cast<const T*>(v.data()), Size);
}

// Casts a byte pointer to a span of given type and size.
template<typename T, size_t Size = std::numeric_limits<size_t>::max()>
[[nodiscard]] constexpr std::span<T, Size> span_cast(std::byte* ptr) noexcept {
    return std::span<T, Size>(reinterpret_cast<T*>(ptr), Size);
}

// Casts a const byte pointer to a const_span of given type and size.
template<typename T, size_t Size = std::numeric_limits<size_t>::max()>
[[nodiscard]] constexpr const_span<T, Size> span_cast(const std::byte* ptr) noexcept {
    return const_span<T, Size>(reinterpret_cast<const T*>(ptr), Size);
}

// Casts a const value of any type to a span of given type and size.
template<typename T, size_t Size = std::numeric_limits<size_t>::max(), typename Val>
[[nodiscard]] constexpr std::span<T, Size> span_cast(const Val& v) noexcept {
    static_assert(Size == std::numeric_limits<size_t>::max() || Size * sizeof(T) <= sizeof(Val));
    constexpr size_t n{ std::min(Size, sizeof(Val) / sizeof(T)) };

    return const_span<T, Size>(reinterpret_cast<const T*>(&v), n);
}

// Casts a const value of any type to a span of given type and size.
template<typename T, size_t Size = std::numeric_limits<size_t>::max(), typename Val>
[[nodiscard]] constexpr std::span<T, Size> span_cast(Val& v) noexcept {
    static_assert(Size == std::numeric_limits<size_t>::max() || Size * sizeof(T) <= sizeof(Val));
    constexpr size_t n{ std::min(Size, sizeof(Val) / sizeof(T)) };

    return std::span<T, Size>(reinterpret_cast<T*>(&v), n);
}

// Creates std::array of bytes from given arguments.
// Arguments values must be in range of a single byte.
template<typename ...Args>
[[nodiscard]] consteval std::array<std::byte, sizeof...(Args)> byte_array(const Args&&... args) noexcept {
    // Workaround over "fold expression did not evaluate to a constant" error
    if (((args > std::numeric_limits<uint8_t>::max()) || ... )) {
        throw "byte_array requires all arguments to be in single byte value range";
    }

    return std::array<std::byte, sizeof...(Args)>{ static_cast<const std::byte>(args)... };
}

// Creates std::vector of bytes from given arguments.
// Arguments values must be in range of a single byte.
// This will check all arguments at runtime, not compile-time.
template<typename ...Args>
[[nodiscard]] constexpr std::vector<std::byte> byte_vector(const Args&&... args) {
    // Workaround over "fold expression did not evaluate to a constant" error
    if (((args > std::numeric_limits<uint8_t>::max()) || ...)) {
        throw "byte_vector requires all arguments to be in single byte value range";
    }

    std::array<std::byte, sizeof...(Args)> arr{ static_cast<const std::byte>(args)... };
    return std::vector<std::byte>{ arr.begin(), arr.end() };
}

// Casts underlying bytes of a given value to a different type.
// Basically just reinterpret_cast, but with static_assert for size.
template<typename T, typename Val>
[[nodiscard]] const T bytes_cast(const Val& ref) noexcept {
    static_assert(sizeof(Val) >= sizeof(T));
    return *reinterpret_cast<const T*>(&ref);
}

Generated by OpenCppCoverage (Version: 0.9.9.0)