#include <byte_buffer.h>

#include <cstdlib>
#include <algorithm>
#include <cstring>
#include <osapi.h>
#include <libb64/cdecode.h>
#include <libb64/cencode.h>

namespace utils
{
byte_buffer::byte_buffer(uint_fast16_t length)
    : _length(length)
{
    // +1 to allow this to be safely printed as a char
    _buffer = reinterpret_cast<unsigned char *>(std::calloc(length + 1, sizeof(unsigned char)));
}

byte_buffer::~byte_buffer()
{
    clear_buffer();
    std::free(_buffer);
}

const bool byte_buffer::is_valid()
{
    return _buffer != nullptr;
}

unsigned char *byte_buffer::get_ptr()
{
    if (is_valid())
    {
        return _buffer;
    }
    else
    {
        return nullptr;
    }
}

/////////////////////////////////////////////////////////////////////////////////////////
// Get the length of the buffer.
//
// Returns: The length of the buffer, or 0 if the buffer is invalid.
/////////////////////////////////////////////////////////////////////////////////////////
const uint_fast16_t byte_buffer::get_length()
{
    if (is_valid())
    {
        return _length;
    }
    else
    {
        return 0;
    }
}

/////////////////////////////////////////////////////////////////////////////////////////
// Get a pointer to a location in the buffer, length is updated to show the length 
// available in that buffer
//
// index: The location to get a pointer to.
// length: The length of the data you expect to access from this pointer.
//
// Returns: A ptr to the buffer, or nullptr if reading that much data would cause 
//          an overflow
/////////////////////////////////////////////////////////////////////////////////////////
unsigned char *byte_buffer::get_ptr(const uint_fast16_t index, uint_fast16_t &length)
{
    unsigned char *ret = nullptr;

    if (is_valid() && _length > index)
    {
        length = _length - index;
        ret = &_buffer[index];
    }
    else
    {
        length = 0;
    }

    return ret;
}

// Copy data into this buffer. The index value is updated to the index of the next point in the buffer
const bool byte_buffer::copy_from(const void *const sourceBuffer, uint_fast16_t &index, const uint_fast16_t &length)
{
    auto ret = false;
    if (is_valid() &&
        sourceBuffer != nullptr &&
        (index + length) <= _length)
    {
        std::memcpy(_buffer + index, sourceBuffer, length);
        index += length;
        ret = true;
    }

    return ret;
}

// Copy data out of this buffer. The index value is updated to the index of the next point in the buffer
const bool byte_buffer::copy_to(void *destinationBuffer, uint_fast16_t &index, const uint_fast16_t &length)
{
    auto ret = false;
    if (is_valid() &&
        destinationBuffer != nullptr &&
        (index + length) < _length)
    {
        std::memcpy(destinationBuffer, _buffer + index, length);
        index += length;
        ret = true;
    }

    return ret;
}

const bool byte_buffer::clone(byte_buffer &bufferToClone)
{
    auto ret = false;
    if (is_valid() &&
        bufferToClone.is_valid() &&
        bufferToClone.get_length() <= _length)
    {
        std::memcpy(bufferToClone.get_ptr(), 0, bufferToClone.get_length());
        ret = true;
    }
    return ret;
}

const void byte_buffer::clear_buffer()
{
    if (is_valid())
    {
        std::fill(_buffer, _buffer + _length, 0);
    }
}

const void byte_buffer::fill_random()
{
    if (is_valid())
    {
        os_get_random(_buffer, _length);
    }
}

// Return the size of the data stored
const uint_fast16_t byte_buffer::load_base64(std::string base64Data)
{
    auto read = 0u;

    if (is_valid() && _length >= base64_decode_expected_len(base64Data.length()))
    {
        base64_decodestate state;
        base64_init_decodestate(&state);
        read = base64_decode_block(base64Data.c_str(), base64Data.length(), reinterpret_cast<char *>(_buffer), &state);
    }

    return read;
}

//// Get a base64 encoded string of this buffer.
const std::string byte_buffer::get_base64()
{
    if (!is_valid())
    {
        return "";
    }

    auto b64String = std::string("");
    auto size = base64_encode_expected_len_nonewlines(_length) + 1;
    auto buffer = reinterpret_cast<char *>(std::calloc(size, 1));
    if (buffer != nullptr)
    {

        base64_encodestate state;
        base64_init_encodestate_nonewlines(&state);

        auto encoded = base64_encode_block(reinterpret_cast<const char *>(_buffer), _length, buffer, &state);
        encoded = base64_encode_blockend(buffer + encoded, &state);

        b64String = std::string(buffer);
        std::free(buffer);
    }

    return b64String;
}

const std::string byte_buffer::to_string()
{
    if (!is_valid())
    {
        return "";
    }

    // Make sure that extra byte really is null!
    _buffer[_length] = '\0';
    return std::string(reinterpret_cast<char *>(_buffer));
}
} // namespace Control_System