#pragma once
#include <gsl/gsl-lite.hpp>
#include <nncase/runtime/k510/gnne_instructions.hpp>

namespace nncase::runtime::k510
{

class span_writer
{

public:
    span_writer()
        : relative_offset_(0)
    {
    }

    template <typename T>
    void write(gsl::span<T> value)
    {
        auto span = gsl::as_bytes(value);
        check_capacity(relative_offset_ + span.size_bytes());
        if (relative_offset_ + span.size_bytes() > stream_.size())
        {
            stream_.resize(relative_offset_ + span.size_bytes());
        }
        std::copy(span.begin(), span.end(), stream_.begin() + relative_offset_);
        relative_offset_ += span.size_bytes();
    }

    template <typename T>
    void write(const T &value)
    {
        write(gsl::byte_span(value));
    }

    void write(const span_writer &other)
    {
        write(other.as_span());
    }

    template <typename T>
    void write(std::vector<T> value)
    {
        // auto span = gsl::make_span(value);
        write(gsl::make_span(value));
    }

    template <typename... TArgs>
    void write(TArgs... args)
    {
        (write(std::as_const(args)), ...);
    }

    size_t size()
    {
        return stream_.size();
    }

    size_t position() const
    {
        return relative_offset_;
    }

    void position(size_t pos)
    {
        relative_offset_ += (size_t)((uint64_t)pos - relative_offset_);
    }

    void skip(size_t len)
    {
        check_capacity(relative_offset_ + len);
        if (relative_offset_ + len > stream_.size())
        {
            stream_.resize(relative_offset_ + len);
        }
        std::fill_n(stream_.begin() + relative_offset_, len, gsl::to_byte(0));
        relative_offset_ += len;
    }

    size_t align_position(size_t alignment)
    {
        size_t pos = position();
        size_t rem = pos % alignment;
        if (rem != 0)
        {
            size_t off = alignment - rem;
            skip(off);
            return off;
        }
        return 0;
    }

    int64_t relative_offset() const noexcept { return relative_offset_; }

    template <typename T = gsl::byte, typename EP = std::remove_cv_t<T>>
    gsl::span<const EP> as_span() const noexcept
    {
        return gsl::make_span(std::data(stream_), relative_offset_).as_span<const EP>();
    }

    /* auto generated */
    void write(const isa::inst_nop &inst);
    void write(const isa::inst_li &inst);
    void write(const isa::inst_intr &inst);
    void write(const isa::inst_end &inst);
    void write(const isa::inst_fence &inst);
    void write(const isa::inst_mmu_conf &inst);
    void write(const isa::inst_fence_ccr &inst);
    void write(const isa::inst_loadif_config &inst);
    void write(const isa::inst_loadif &inst);
    void write(const isa::inst_load &inst);
    void write(const isa::inst_loadif_compress_conf &inst);
    void write(const isa::inst_load_compress_conf &inst);
    void write(const isa::inst_store &inst);
    void write(const isa::inst_store_t_config &inst);
    void write(const isa::inst_store_t &inst);
    void write(const isa::inst_store_t_compress_conf &inst);
    void write(const isa::inst_store_compress_conf &inst);
    void write(const isa::inst_tcu_dm_broadcast &inst);
    void write(const isa::inst_tcu_dm_conf_if &inst);
    void write(const isa::inst_tcu_dm_fetchif &inst);
    void write(const isa::inst_tcu_dm_conf_w &inst);
    void write(const isa::inst_tcu_dm_fetchw &inst);
    void write(const isa::inst_tcu_dm_conf_of &inst);
    void write(const isa::inst_tcu_pu_conf &inst);
    void write(const isa::inst_tcu_pu_conf_act &inst);
    void write(const isa::inst_tcu_pu_compute &inst);
    void write(const isa::inst_tcu_dot_dm_if_conf &inst);
    void write(const isa::inst_tcu_dot_dm_of_conf &inst);
    void write(const isa::inst_tcu_dot_dm_fetch_src1 &inst);
    void write(const isa::inst_tcu_dot_dm_fetch_src2 &inst);
    void write(const isa::inst_tcu_pu_compute_dummy &inst);
    void write(const isa::inst_mfu_mn_map_compute &inst);
    void write(const isa::inst_mfu_mn_vmap_compute &inst);
    void write(const isa::inst_mfu_reduce &inst);
    void write(const isa::inst_mfu_vreduce &inst);
    void write(const isa::inst_mfu_mn_broadcast_compute &inst);
    void write(const isa::inst_mfu_mn_reduce &inst);
    void write(const isa::inst_mfu_mn_conf &inst);
    void write(const isa::inst_mfu_mnop_conf &inst);
    void write(const isa::inst_mfu_pdp_conf &inst);
    void write(const isa::inst_mfu_pdp_src_conf &inst);
    void write(const isa::inst_mfu_pdp_reduce &inst);
    void write(const isa::inst_mfu_mn_broadcast_conf &inst);
    void write(const isa::inst_mfu_crop &inst);
    void write(const isa::inst_mfu_memset &inst);
    void write(const isa::inst_mfu_memcpy &inst);
    void write(const isa::inst_mfu_trans &inst);
    void write(const isa::inst_mfu_mn_conf2 &inst);

private:
    std::vector<gsl::byte> stream_;

    int64_t relative_offset_;

    void check_capacity(size_t target_capa)
    {
        while (stream_.capacity() < target_capa)
        {
            stream_.reserve(stream_.capacity() * 2 + 1);
        }
    }
};

}