//------------------------------------------------------------------------------
// Automatically generated by the Fast Binary Encoding compiler, do not modify!
// https://github.com/chronoxor/FastBinaryEncoding
// Source: FBE
// FBE version: 1.14.5.0
//------------------------------------------------------------------------------

#include "fbe.h"

#if defined(_WIN32) || defined(_WIN64)
#include <windows.h>
#include <rpc.h>
#undef DELETE
#undef ERROR
#undef Yield
#undef min
#undef max
#undef uuid_t
#endif

namespace FBE {

std::string buffer_t::base64encode() const
{
    const char base64[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
    std::string result;

    int val = 0;
    int valb = -6;
    for (auto c : _data)
    {
        val = (val << 8) + c;
        valb += 8;
        while (valb >= 0)
        {
            result.push_back(base64[(val >> valb) & 0x3F]);
            valb -= 6;
        }
    }

    if (valb > -6)
        result.push_back(base64[((val << 8) >> (valb + 8)) & 0x3F]);

    while (result.size() % 4)
        result.push_back('=');

    return result;
}

buffer_t buffer_t::base64decode(const std::string& str)
{
    const char base64[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
    buffer_t result;

    std::vector<int> pattern(256, -1);
    for (int i = 0; i < 64; ++i)
        pattern[base64[i]] = i;

    int val = 0;
    int valb = -8;
    for (auto c : str)
    {
        if (pattern[c] == -1)
            break;

        val = (val << 6) + pattern[c];
        valb += 6;

        if (valb >= 0)
        {
            result.push_back((uint8_t)((val >> valb) & 0xFF));
            valb -= 8;
        }
    }

    return result;
}

uint64_t utc()
{
#if defined(unix) || defined(__unix) || defined(__unix__) || defined(__APPLE__)
    struct timespec timestamp;
    if (clock_gettime(CLOCK_REALTIME, &timestamp) != 0)
        throw std::runtime_error("Cannot get value of CLOCK_REALTIME timer!");
    return (timestamp.tv_sec * 1000000000) + timestamp.tv_nsec;
#elif defined(_WIN32) || defined(_WIN64)
    FILETIME ft;
#if (_WIN32_WINNT >= _WIN32_WINNT_WIN8)
    GetSystemTimePreciseAsFileTime(&ft);
#else
    GetSystemTimeAsFileTime(&ft);
#endif

    ULARGE_INTEGER result;
    result.LowPart = ft.dwLowDateTime;
    result.HighPart = ft.dwHighDateTime;
    return (result.QuadPart - 116444736000000000ull) * 100;
#endif
}

uint8_t unhex(char ch)
{
    if ((ch >= '0') && (ch <= '9'))
        return ch - '0';
    else if ((ch >= 'a') && (ch <= 'f'))
        return 10 + ch - 'a';
    else if ((ch >= 'A') && (ch <= 'F'))
        return 10 + ch - 'A';
    else
        return 255;
}

uuid_t::uuid_t(const std::string& uuid)
{
    char v1 = 0;
    char v2 = 0;
    bool pack = false;
    size_t index = 0;

    // Parse UUID string
    for (auto ch : uuid)
    {
        if ((ch == '-') || (ch == '{') || (ch == '}'))
            continue;

        if (pack)
        {
            v2 = ch;
            pack = false;
            uint8_t ui1 = unhex(v1);
            uint8_t ui2 = unhex(v2);
            if ((ui1 > 15) || (ui2 > 15))
                throw std::invalid_argument("Invalid UUID string: " + uuid);
            _data[index++] = ui1 * 16 + ui2;
            if (index >= 16)
                break;
        }
        else
        {
            v1 = ch;
            pack = true;
        }
    }

    // Fill remaining data with zeros
    for (; index < 16; ++index)
        _data[index++] = 0;
}

std::string uuid_t::string() const
{
    const char* digits = "0123456789abcdef";

    std::string result(36, '0');

    int index = 0;
    for (auto value : _data)
    {
        result[index++] = digits[(value >> 4) & 0x0F];
        result[index++] = digits[(value >> 0) & 0x0F];
        if ((index == 8) || (index == 13) || (index == 18) || (index == 23))
            result[index++] = '-';
    }

    return result;
}

uuid_t uuid_t::sequential()
{
    uuid_t result;
#if defined(unix) || defined(__unix) || defined(__unix__) || defined(__APPLE__)
    ::uuid_t uuid;
    uuid_generate_time(uuid);
    result._data[0] = uuid[0];
    result._data[1] = uuid[1];
    result._data[2] = uuid[2];
    result._data[3] = uuid[3];
    result._data[4] = uuid[4];
    result._data[5] = uuid[5];
    result._data[6] = uuid[6];
    result._data[7] = uuid[7];
    result._data[8] = uuid[8];
    result._data[9] = uuid[9];
    result._data[10] = uuid[10];
    result._data[11] = uuid[11];
    result._data[12] = uuid[12];
    result._data[13] = uuid[13];
    result._data[14] = uuid[14];
    result._data[15] = uuid[15];
#elif defined(_WIN32) || defined(_WIN64)
    ::UUID uuid;
    if (UuidCreateSequential(&uuid) != RPC_S_OK)
        throw std::runtime_error("Cannot generate sequential UUID!");

    result._data[0] = (uuid.Data1 >> 24) & 0xFF;
    result._data[1] = (uuid.Data1 >> 16) & 0xFF;
    result._data[2] = (uuid.Data1 >>  8) & 0xFF;
    result._data[3] = (uuid.Data1 >>  0) & 0xFF;
    result._data[4] = (uuid.Data2 >>  8) & 0xFF;
    result._data[5] = (uuid.Data2 >>  0) & 0xFF;

    result._data[6] = (uuid.Data3 >>  8) & 0xFF;
    result._data[7] = (uuid.Data3 >>  0) & 0xFF;

    result._data[8] = uuid.Data4[0];
    result._data[9] = uuid.Data4[1];

    result._data[10] = uuid.Data4[2];
    result._data[11] = uuid.Data4[3];
    result._data[12] = uuid.Data4[4];
    result._data[13] = uuid.Data4[5];
    result._data[14] = uuid.Data4[6];
    result._data[15] = uuid.Data4[7];
#endif
    return result;
}

uuid_t uuid_t::random()
{
    uuid_t result;
#if defined(unix) || defined(__unix) || defined(__unix__) || defined(__APPLE__)
    ::uuid_t uuid;
    uuid_generate_random(uuid);
    result._data[0] = uuid[0];
    result._data[1] = uuid[1];
    result._data[2] = uuid[2];
    result._data[3] = uuid[3];
    result._data[4] = uuid[4];
    result._data[5] = uuid[5];
    result._data[6] = uuid[6];
    result._data[7] = uuid[7];
    result._data[8] = uuid[8];
    result._data[9] = uuid[9];
    result._data[10] = uuid[10];
    result._data[11] = uuid[11];
    result._data[12] = uuid[12];
    result._data[13] = uuid[13];
    result._data[14] = uuid[14];
    result._data[15] = uuid[15];
#elif defined(_WIN32) || defined(_WIN64)
    ::UUID uuid;
    if (UuidCreate(&uuid) != RPC_S_OK)
        throw std::runtime_error("Cannot generate random UUID!");

    result._data[0] = (uuid.Data1 >> 24) & 0xFF;
    result._data[1] = (uuid.Data1 >> 16) & 0xFF;
    result._data[2] = (uuid.Data1 >>  8) & 0xFF;
    result._data[3] = (uuid.Data1 >>  0) & 0xFF;
    result._data[4] = (uuid.Data2 >>  8) & 0xFF;
    result._data[5] = (uuid.Data2 >>  0) & 0xFF;

    result._data[6] = (uuid.Data3 >>  8) & 0xFF;
    result._data[7] = (uuid.Data3 >>  0) & 0xFF;

    result._data[8] = uuid.Data4[0];
    result._data[9] = uuid.Data4[1];

    result._data[10] = uuid.Data4[2];
    result._data[11] = uuid.Data4[3];
    result._data[12] = uuid.Data4[4];
    result._data[13] = uuid.Data4[5];
    result._data[14] = uuid.Data4[6];
    result._data[15] = uuid.Data4[7];
#endif
    return result;
}

#if defined(LOGGING_PROTOCOL)
CppLogging::Record& operator<<(CppLogging::Record& record, const uuid_t& uuid)
{
    const char* digits = "0123456789abcdef";

    std::array<char, 36> result;

    int index = 0;
    for (auto value : uuid.data())
    {
        result[index++] = digits[(value >> 4) & 0x0F];
        result[index++] = digits[(value >> 0) & 0x0F];
        if ((index == 8) || (index == 13) || (index == 18) || (index == 23))
            result[index++] = '-';
    }

    return record.StoreCustom(std::string_view(result.data(), result.size()));
}
#endif

void FBEBuffer::attach(const void* data, size_t size, size_t offset)
{
    assert((data != nullptr) && "Invalid buffer!");
    if (data == nullptr)
        throw std::invalid_argument("Invalid buffer!");
    assert((size > 0) && "Invalid size!");
    if (size == 0)
        throw std::invalid_argument("Invalid size!");
    assert((offset <= size) && "Invalid offset!");
    if (offset > size)
        throw std::invalid_argument("Invalid offset!");

    _data = (uint8_t*)data;
    _capacity = 0;
    _size = size;
    _offset = offset;
}

void FBEBuffer::attach(const std::vector<uint8_t>& buffer, size_t offset)
{
    assert((buffer.data() != nullptr) && "Invalid buffer!");
    if (buffer.data() == nullptr)
        throw std::invalid_argument("Invalid buffer!");
    assert((buffer.size() > 0) && "Invalid size!");
    if (buffer.size() == 0)
        throw std::invalid_argument("Invalid size!");
    assert((offset <= buffer.size()) && "Invalid offset!");
    if (offset > buffer.size())
        throw std::invalid_argument("Invalid offset!");

    _data = (uint8_t*)buffer.data();
    _capacity = 0;
    _size = buffer.size();
    _offset = offset;
}

void FBEBuffer::clone(const void* data, size_t size, size_t offset)
{
    assert((offset <= size) && "Invalid offset!");
    if (offset > size)
        throw std::invalid_argument("Invalid offset!");

    reserve(size);
    std::memcpy(_data, data, size);
    _capacity = size;
    _size = size;
    _offset = offset;
}

void FBEBuffer::clone(const std::vector<uint8_t>& buffer, size_t offset)
{
    assert((offset <= buffer.size()) && "Invalid offset!");
    if (offset > buffer.size())
        throw std::invalid_argument("Invalid offset!");

    size_t size = buffer.size();

    reserve(size);
    std::memcpy(_data, buffer.data(), size);
    _capacity = size;
    _size = size;
    _offset = offset;
}

size_t FBEBuffer::allocate(size_t size)
{
    size_t offset = _size;

    // Calculate a new buffer size
    size_t total = _size + size;

    if (total <= _capacity)
    {
        _size = total;
        return offset;
    }

    _capacity = std::max(total, 2 * _capacity);
    uint8_t* data = (uint8_t*)std::malloc(_capacity);
    std::memcpy(data, _data, _size);
    std::free(_data);
    _data = data;
    _size = total;
    return offset;
}

void FBEBuffer::remove(size_t offset, size_t size)
{
    assert(((offset + size) <= _size) && "Invalid offset & size!");
    if ((offset + size) > _size)
        throw std::invalid_argument("Invalid offset & size!");

    std::memcpy(_data + offset, _data + offset + size, _size - size - offset);
    _size -= size;
    if (_offset >= (offset + size))
        _offset -= size;
    else if (_offset >= offset)
    {
        _offset -= _offset - offset;
        if (_offset > _size)
            _offset = _size;
    }
}

void FBEBuffer::reserve(size_t capacity)
{
    if (capacity > _capacity)
    {
        _capacity = std::max(capacity, 2 * _capacity);
        uint8_t* data = (uint8_t*)std::malloc(_capacity);
        std::memcpy(data, _data, _size);
        std::free(_data);
        _data = data;
    }
}

void FBEBuffer::resize(size_t size)
{
    reserve(size);
    _size = size;
    if (_offset > _size)
        _offset = _size;
}

void FBEBuffer::reset()
{
    _size = 0;
    _offset = 0;
}

} // namespace FBE
