#pragma once

#include <vector>
#include <cassert>
#include <cstring>
#include <memory>

namespace data {

/// Map vector to istream
struct vec_istream: std::streambuf {
    vec_istream(const std::vector<char> &vec) {
        this->setg((char*)&vec[0], (char*)&vec[0], (char*)&vec[0] + vec.size());
    }
};

/// Abstract data with moving cursor
struct view {
    view(size_t size): siz(size) { }

    size_t siz;
    size_t cur = 0;

    constexpr size_t size() const { return siz; }
    constexpr bool isDone() const { return cur >= siz; }
};
/// Vector with in_view interface
struct in_vector {
    /// 512 Mébibits
    static constexpr size_t MAX_SIZE = 1ul << 26;

    in_vector(size_t max_size = MAX_SIZE): max_size(max_size) {}

    std::vector<uint8_t> data;
    size_t max_size;

    uint8_t* writeTo(size_t len) {
        const auto offset = data.size();
        assert(offset + len <= max_size);
        data.resize(offset + len);
        return data.data() + offset;
    }
    void write(const uint8_t* data, size_t len) {
        memcpy(writeTo(len), data, len);
    }
};
// Abstract data writer
struct in_view: view {
    in_view(uint8_t *ptr, size_t size): view(size), ptr(ptr) {
        assert(ptr != nullptr);
    }

    uint8_t* ptr;
    uint8_t* writeTo(size_t len) {
        assert(cur + len <= siz);
        auto p = ptr + cur;
        cur += len;
        return p;
    }
    void write(const uint8_t* data, size_t len) {
        memcpy(writeTo(len), data, len);
    }
};
/// Abstract data reader
struct out_view: view {
    out_view(): out_view(nullptr, 0) { }
    out_view(const uint8_t *ptr, size_t size): view(size), ptr(ptr) { }

    const uint8_t* ptr;
    const uint8_t* readFrom(size_t len) {
        assert(cur + len <= siz);
        auto p = ptr + cur;
        cur += len;
        return p;
    }
    void read(uint8_t* out, size_t len) {
        memcpy(out, readFrom(len), len);
    }

    constexpr const uint8_t* data() const { return ptr; }
    constexpr size_t remaining() const { return size() - cur; }
};
/// Pointer to opaque owned memory
using handle_t = std::shared_ptr<const void>;
/// out_view with owned data
struct out_buffer: out_view {
    out_buffer(): out_view() { }
    out_buffer(const out_view& view, const handle_t& handle):
        out_view(view), handle(handle) { }
    /// Take view ownership
    out_buffer(const out_view& view):
        out_buffer(view, std::shared_ptr<const void>(view.ptr)) { }
    /// Take ptr ownership
    out_buffer(const uint8_t *ptr, size_t size): out_buffer(out_view(ptr, size)) { }

    handle_t handle = nullptr;
};
}