#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; }
};
// 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);
    }
};
/// Writer with Dynamic allocation and subparts
struct in_writer: public in_view {
private:
    static constexpr auto EMPTY_OFFSET = 0x1000;
    static uint8_t* alloc(size_t size) { return (uint8_t*)(size > 0 ? ::malloc(size) : reinterpret_cast<uint8_t*>(EMPTY_OFFSET)); }
    static uint8_t* realloc(uint8_t* ptr, size_t size) { return (uint8_t*)::realloc(ptr, size); }
    static void free(uint8_t* ptr) { ::free(ptr); }

    inline void auto_size(size_t len) {
        if (cur + len > siz)
            resize(cur + len);
    }

public:
    in_writer(size_t init_size = 0): in_view(alloc(init_size), init_size), vis_siz(init_size) { }
    ~in_writer() { if (ptr != reinterpret_cast<uint8_t*>(EMPTY_OFFSET)) free(ptr); }

    size_t vis_siz = 0;

    uint8_t* data() { return ptr; }
    constexpr size_t size() const { return vis_siz; }

    void write(const void* data, size_t len) {
        in_view::write((uint8_t*)data, len);
    }
    /// Write without resize
    template<typename D>
    void write(const D& d) {
        write(&d, sizeof(d));
    }

    uint8_t* pushTo(size_t len) {
        auto_size(len);
        return writeTo(len);
    }
    void push(const void* data, size_t len) {
        auto_size(len);
        in_view::write((uint8_t*)data, len);
    }
    template<typename D>
    void push(const D& d) {
        push(&d, sizeof(d));
    }

    size_t getCursor() const { return cur; }
    void writeAt(size_t pos, const void* data, size_t len) {
        assert(pos + len <= vis_siz);
        memcpy(ptr + pos, data, len);
    }
    template<typename D>
    void writeAt(size_t pos, const D& d) {
        writeAt(pos, &d, sizeof(d));
    }

    void reserve(size_t target) {
        if (target >= siz) {
            ptr = realloc(ptr, target);
            siz = target;
        }
    }
    void resize(size_t target) {
        reserve(target);
        vis_siz = target;
    }

    bool isFull() const {
        return cur >= vis_siz;
    }

    // Must take ptr ownership before
    void reset() {
        ptr = reinterpret_cast<uint8_t*>(EMPTY_OFFSET);
        cur = 0;
        siz = 0;
        vis_siz = 0;
    }

    struct part {
        part(in_writer* parent): up(parent), start(parent->cur) { }
        in_writer* up;
        size_t start;

        uint8_t* data() { return up->data() + start; }
        constexpr size_t size() const { return up->size() - start; }

        void reserve(size_t size) {
            up->reserve(start + size);
        }
        void resize(size_t size) {
            up->resize(start + size);
        }
    };
    template<typename CB>
    size_t pushPart(const CB& callback) {
        const auto prev = cur;
        callback(part(this));
        cur = vis_siz;
        return cur - prev;
    }
    template<typename CB>
    size_t pushSizedPart(const CB& callback) {
        const auto size_cur = cur;
        push<size_t>(0);
        const size_t size = pushPart(callback);
        writeAt(size_cur, size);
        return size;
    }
};
/// 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 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; }
};
/// Helper to read from out_view
struct out_reader: public out_view {
    out_reader(const out_view& buf): out_view(buf.ptr, buf.siz) { cur = buf.cur; }

    template<typename D>
    const D* read() {
        return cur + sizeof(D) <= size() ?
            (const D*)readFrom(sizeof(D)) : nullptr;
    }
    template<typename D>
    bool read(D& out) {
        const auto ptr = read<D>();
        if (ptr == nullptr)
            return false;

        out = *ptr;
        return true;
    }

    bool skip(size_t size) {
        readFrom(size);
        return !isDone();
    }

    data::out_view readPart(size_t size) {
        return data::out_view(readFrom(size), size);
    }
    data::out_view readSizedPart() {
        size_t size = 0;
        read(size);
        return readPart(size);
    }
    data::out_view readRemaining() {
        return readPart(remaining());
    }
};
/// 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)) { }
    /// Take ptr ownership (release writer)
    out_buffer(in_writer& writer):
        out_buffer(writer.data(), writer.size()) { writer.reset(); }

    handle_t handle = nullptr;
};
}