#include "foundation/binary_stream.h"

#include <cstdint>

namespace pp::foundation {

namespace {

[[nodiscard]] bool overlaps_backing_storage(
    const std::vector<std::byte>& backing,
    std::span<const std::byte> bytes) noexcept
{
    if (backing.empty() || bytes.empty()) {
        return false;
    }

    const auto backing_begin = reinterpret_cast<std::uintptr_t>(backing.data());
    const auto backing_end = backing_begin + backing.size();
    const auto bytes_begin = reinterpret_cast<std::uintptr_t>(bytes.data());
    const auto bytes_end = bytes_begin + bytes.size();
    return bytes_begin < backing_end && backing_begin < bytes_end;
}

}

ByteReader::ByteReader(std::span<const std::byte> bytes) noexcept
    : bytes_(bytes)
{
}

std::size_t ByteReader::position() const noexcept
{
    return position_;
}

std::size_t ByteReader::size() const noexcept
{
    return bytes_.size();
}

std::size_t ByteReader::remaining() const noexcept
{
    return bytes_.size() - position_;
}

bool ByteReader::empty() const noexcept
{
    return remaining() == 0;
}

Status ByteReader::seek(std::size_t position) noexcept
{
    if (position > bytes_.size()) {
        return Status::out_of_range("seek position is outside the stream");
    }

    position_ = position;
    return Status::success();
}

Result<std::uint8_t> ByteReader::read_u8() noexcept
{
    const auto bytes = read_bytes(1);
    if (!bytes) {
        return Result<std::uint8_t>::failure(bytes.status());
    }

    return Result<std::uint8_t>::success(static_cast<std::uint8_t>(bytes.value()[0]));
}

Result<std::uint16_t> ByteReader::read_u16_le() noexcept
{
    const auto bytes = read_bytes(2);
    if (!bytes) {
        return Result<std::uint16_t>::failure(bytes.status());
    }

    const auto b0 = static_cast<std::uint16_t>(bytes.value()[0]);
    const auto b1 = static_cast<std::uint16_t>(bytes.value()[1]);
    return Result<std::uint16_t>::success(static_cast<std::uint16_t>(b0 | (b1 << 8U)));
}

Result<std::uint32_t> ByteReader::read_u32_le() noexcept
{
    const auto bytes = read_bytes(4);
    if (!bytes) {
        return Result<std::uint32_t>::failure(bytes.status());
    }

    const auto b0 = static_cast<std::uint32_t>(bytes.value()[0]);
    const auto b1 = static_cast<std::uint32_t>(bytes.value()[1]);
    const auto b2 = static_cast<std::uint32_t>(bytes.value()[2]);
    const auto b3 = static_cast<std::uint32_t>(bytes.value()[3]);
    return Result<std::uint32_t>::success(b0 | (b1 << 8U) | (b2 << 16U) | (b3 << 24U));
}

Result<std::span<const std::byte>> ByteReader::read_bytes(std::size_t count) noexcept
{
    if (count > remaining()) {
        return Result<std::span<const std::byte>>::failure(
            Status::out_of_range("read would move beyond the end of the stream"));
    }

    const auto start = position_;
    position_ += count;
    return Result<std::span<const std::byte>>::success(bytes_.subspan(start, count));
}

ByteWriter::ByteWriter(std::vector<std::byte>& bytes) noexcept
    : bytes_(&bytes)
{
}

std::size_t ByteWriter::size() const noexcept
{
    return bytes_ == nullptr ? 0 : bytes_->size();
}

Status ByteWriter::write_u8(std::uint8_t value)
{
    if (bytes_ == nullptr) {
        return Status::invalid_argument("writer has no backing storage");
    }

    bytes_->push_back(static_cast<std::byte>(value));
    return Status::success();
}

Status ByteWriter::write_u16_le(std::uint16_t value)
{
    if (bytes_ == nullptr) {
        return Status::invalid_argument("writer has no backing storage");
    }

    bytes_->push_back(static_cast<std::byte>(value & 0xffU));
    bytes_->push_back(static_cast<std::byte>((value >> 8U) & 0xffU));
    return Status::success();
}

Status ByteWriter::write_u32_le(std::uint32_t value)
{
    if (bytes_ == nullptr) {
        return Status::invalid_argument("writer has no backing storage");
    }

    bytes_->push_back(static_cast<std::byte>(value & 0xffU));
    bytes_->push_back(static_cast<std::byte>((value >> 8U) & 0xffU));
    bytes_->push_back(static_cast<std::byte>((value >> 16U) & 0xffU));
    bytes_->push_back(static_cast<std::byte>((value >> 24U) & 0xffU));
    return Status::success();
}

Status ByteWriter::write_bytes(std::span<const std::byte> bytes)
{
    if (bytes_ == nullptr) {
        return Status::invalid_argument("writer has no backing storage");
    }

    if (overlaps_backing_storage(*bytes_, bytes)) {
        const std::vector<std::byte> copy(bytes.begin(), bytes.end());
        bytes_->insert(bytes_->end(), copy.begin(), copy.end());
        return Status::success();
    }

    bytes_->insert(bytes_->end(), bytes.begin(), bytes.end());
    return Status::success();
}

}
