#include "InstrReelDyn.hpp"

#include <spider/runtime/memory/Types.hpp>

namespace spider {

    InstrReelDyn::InstrReelDyn(u64 length) : _size(length) {
        // Safe int ceil division
        growTo((length >> 8) + ((length & 255) != 0));
    }

    InstrReelDyn::InstrReelDyn(const u8* data, u64 length) {}

    InstrReelDyn::InstrReelDyn(const InstrReelDyn& copy)
        : _blocks(copy._blocks), _size(copy._size) {
    }

    InstrReelDyn::InstrReelDyn(InstrReelDyn&& move) noexcept
        : _blocks(std::move(move._blocks)), _size(std::move(move._size)) {
    }

    InstrReelDyn::~InstrReelDyn() {
        // .. //
    }

    InstrReelDyn& InstrReelDyn::operator=(const InstrReelDyn& copy) {
        _blocks = copy._blocks;
        _size = copy._size;
        return *this;
    }

    InstrReelDyn& InstrReelDyn::operator=(InstrReelDyn&& move) noexcept {
        _blocks = std::move(move._blocks);
        _size = std::move(move._size);
        return *this;
    }

    void InstrReelDyn::growTo(u64 ip) {
        u64 b_index = (ip >> 8) + 1;
        while (_blocks.size() < b_index) {
            _blocks.emplace_back();
        }
        if (ip >= _size) _size = ip + 1;
    }

    std::pair<u64, u8> InstrReelDyn::indexOf(u64 ip) {
        return { ip >> 8, ip & 0xFF }; // { ip / 256, ip % 256 };
    }

    bool InstrReelDyn::continous(u64 ip0, u64 ip1, u64* b_index, u16* s_index) {
        auto i = indexOf(ip0);
        *b_index = i.first;
        *s_index = i.second;
        return i.first == (ip1 >> 8);
    }

    // Particular Cases

    /**
     * Obtains a byte of data at
     * the specific location.
     * Reindexing may occur, continous access
     * may incurr in less penalties.
     */
    u8 InstrReelDyn::readU8(u64 ip) {
        if (ip + 1 > _size) return 0;
        auto i = indexOf(ip);
        return _blocks[i.first].data[i.second];
    }

    /**
     * Obtains a byte of data at
     * the specific location.
     * Reindexing may occur, continous access
     * may incurr in less penalties.
     */
    u16 InstrReelDyn::readU16(u64 ip) {
        if (ip + 2 > _size) return 0;

        u16 dat;
        u64 b_index;
        u16 s_index;

        if (continous(ip, ip + 1, &b_index, &s_index)) {
            spider::loadLE(&dat, &_blocks[b_index].data[s_index]);
            return dat;
        }

        dat = 0;
        for (isize i = 0; i < sizeof(dat); i++) {
            auto& b = _blocks[(b_index + s_index) >> 8];
            dat |= u16(b.data[s_index++ & 0xFF]) << (i * 8);
        }
        return dat;
    }

    /**
     * Obtains a byte of data at
     * the specific location.
     * Reindexing may occur, continous access
     * may incurr in less penalties.
     */
    u32 InstrReelDyn::readU32(u64 ip) {
        if (ip + 4 > _size) return 0;

        u32 dat;
        u64 b_index;
        u16 s_index;

        if (continous(ip, ip + 3, &b_index, &s_index)) {
            spider::loadLE(&dat, &_blocks[b_index].data[s_index]);
            return dat;
        }

        dat = 0;
        for (isize i = 0; i < sizeof(dat); i++) {
            auto& b = _blocks[(b_index + s_index) >> 8];
            dat |= u32(b.data[s_index++ & 0xFF]) << (i * 8);
        }
        return dat;
    }

    /**
     * Obtains a byte of data at
     * the specific location.
     * Reindexing may occur, continous access
     * may incurr in less penalties.
     */
    u64 InstrReelDyn::readU64(u64 ip) {
        if (ip + 8 > _size) return 0;

        u64 dat;
        u64 b_index;
        u16 s_index;

        if (continous(ip, ip + 7, &b_index, &s_index)) {
            spider::loadLE(&dat, &_blocks[b_index].data[s_index]);
            return dat;
        }

        dat = 0;
        for (isize i = 0; i < sizeof(dat); i++) {
            auto& b = _blocks[(b_index + s_index) >> 8];
            dat |= u64(b.data[s_index++ & 0xFF]) << (i * 8);
        }
        return dat;
    }

    /**
     * Reads a range of data, and
     * outputs it.
     */
    void InstrReelDyn::readRange(u64 ip, u8* out, u64 length) {
        if (ip + length > _size) {
            std::memset(out, 0, length);
            return;
        }

        u64 b_index;
        u16 s_index;

        if (continous(ip, ip + length, &b_index, &s_index)) {
            std::memcpy(out, &_blocks[b_index].data[s_index], length);
            return;
        }

        u64 bytes_read = 0;
        while (bytes_read < length) {
            u64 remaining_in_block = 256 - s_index;
            u64 chunk_size = std::min(remaining_in_block, length - bytes_read);

            // Perform bulk copy for the current segment
            std::memcpy(out + bytes_read, &_blocks[b_index].data[s_index], chunk_size);

            // Advance pointers
            bytes_read += chunk_size;
            b_index++;
            s_index = 0; // reset
        }
    }

    /**
     * Current size of the instructions.
     */
    u64 InstrReelDyn::size() {
        return _size;
    }

    // Mutation //

    // TODO!

    void InstrReelDyn::writeU8(u64 ip, u8 dat) {}

    void InstrReelDyn::writeU16(u64 ip, u16 dat) {}

    void InstrReelDyn::writeU32(u64 ip, u32 dat) {}

    void InstrReelDyn::writeU64(u64 ip, u64 dat) {}

    /**
     * Appends instruction at the end.
     */
    void InstrReelDyn::append(u16 bc) {}

}
